From 2d8fe8a941a09447ad8d6eb78c9d3f087288de46 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Tue, 15 Apr 2025 10:38:48 +0200 Subject: [PATCH 01/54] fix(backend): block/mute chat (#8364) * no notification when blocked * no notifications for muted users * move tests from emssage to notification middleware * fix test * also dont update unreadRoomCount when user is muted --------- Co-authored-by: mahula --- .../notificationsMiddleware.spec.ts | 107 +++++++++++++++--- .../notifications/notificationsMiddleware.ts | 30 +++-- backend/src/schema/resolvers/messages.spec.ts | 24 +--- backend/src/schema/resolvers/messages.ts | 20 +--- backend/src/schema/resolvers/rooms.spec.ts | 28 +++++ backend/src/schema/resolvers/rooms.ts | 5 +- 6 files changed, 154 insertions(+), 60 deletions(-) diff --git a/backend/src/middleware/notifications/notificationsMiddleware.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.spec.ts index c636a7c87..90888cf8b 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.spec.ts @@ -31,8 +31,10 @@ jest.mock('../helpers/isUserOnline', () => ({ isUserOnline: () => isUserOnlineMock(), })) +const pubsubSpy = jest.spyOn(pubsub, 'publish') + let server, query, mutate, notifiedUser, authenticatedUser -let publishSpy + const driver = getDriver() const neode = getNeode() const categoryIds = ['cat9'] @@ -65,7 +67,6 @@ const createCommentMutation = gql` beforeAll(async () => { await cleanDatabase() - publishSpy = jest.spyOn(pubsub, 'publish') const createServerResult = createServer({ context: () => { return { @@ -87,7 +88,6 @@ afterAll(async () => { }) beforeEach(async () => { - publishSpy.mockClear() notifiedUser = await Factory.build( 'user', { @@ -417,7 +417,7 @@ describe('notifications', () => { it('publishes `NOTIFICATION_ADDED` to me', async () => { await createPostAction() - expect(publishSpy).toHaveBeenCalledWith( + expect(pubsubSpy).toHaveBeenCalledWith( 'NOTIFICATION_ADDED', expect.objectContaining({ notificationAdded: expect.objectContaining({ @@ -428,7 +428,7 @@ describe('notifications', () => { }), }), ) - expect(publishSpy).toHaveBeenCalledTimes(1) + expect(pubsubSpy).toHaveBeenCalledTimes(1) }) describe('updates the post and mentions me again', () => { @@ -578,7 +578,7 @@ describe('notifications', () => { it('does not publish `NOTIFICATION_ADDED`', async () => { await createPostAction() - expect(publishSpy).not.toHaveBeenCalled() + expect(pubsubSpy).not.toHaveBeenCalled() }) }) }) @@ -722,7 +722,7 @@ describe('notifications', () => { it('does not publish `NOTIFICATION_ADDED` to authenticated user', async () => { await createCommentOnPostAction() - expect(publishSpy).toHaveBeenCalledWith( + expect(pubsubSpy).toHaveBeenCalledWith( 'NOTIFICATION_ADDED', expect.objectContaining({ notificationAdded: expect.objectContaining({ @@ -733,14 +733,14 @@ describe('notifications', () => { }), }), ) - expect(publishSpy).toHaveBeenCalledTimes(1) + expect(pubsubSpy).toHaveBeenCalledTimes(1) }) }) }) }) }) - describe('chat email notifications', () => { + describe('chat notifications', () => { let chatSender let chatReceiver let roomId @@ -779,7 +779,7 @@ describe('notifications', () => { }) describe('if the chatReceiver is online', () => { - it('sends no email', async () => { + it('publishes subscriptions but sends no email', async () => { isUserOnlineMock = jest.fn().mockReturnValue(true) await mutate({ @@ -790,13 +790,32 @@ describe('notifications', () => { }, }) + expect(pubsubSpy).toHaveBeenCalledWith('ROOM_COUNT_UPDATED', { + roomCountUpdated: '1', + userId: 'chatReceiver', + }) + expect(pubsubSpy).toHaveBeenCalledWith('CHAT_MESSAGE_ADDED', { + chatMessageAdded: expect.objectContaining({ + id: expect.any(String), + content: 'Some nice message to chatReceiver', + senderId: 'chatSender', + username: 'chatSender', + avatar: null, + date: expect.any(String), + saved: true, + distributed: false, + seen: false, + }), + userId: 'chatReceiver', + }) + expect(sendMailMock).not.toHaveBeenCalled() expect(chatMessageTemplateMock).not.toHaveBeenCalled() }) }) describe('if the chatReceiver is offline', () => { - it('sends an email', async () => { + it('publishes subscriptions and sends an email', async () => { isUserOnlineMock = jest.fn().mockReturnValue(false) await mutate({ @@ -807,13 +826,32 @@ describe('notifications', () => { }, }) + expect(pubsubSpy).toHaveBeenCalledWith('ROOM_COUNT_UPDATED', { + roomCountUpdated: '1', + userId: 'chatReceiver', + }) + expect(pubsubSpy).toHaveBeenCalledWith('CHAT_MESSAGE_ADDED', { + chatMessageAdded: expect.objectContaining({ + id: expect.any(String), + content: 'Some nice message to chatReceiver', + senderId: 'chatSender', + username: 'chatSender', + avatar: null, + date: expect.any(String), + saved: true, + distributed: false, + seen: false, + }), + userId: 'chatReceiver', + }) + expect(sendMailMock).toHaveBeenCalledTimes(1) expect(chatMessageTemplateMock).toHaveBeenCalledTimes(1) }) }) describe('if the chatReceiver has blocked chatSender', () => { - it('sends no email', async () => { + it('publishes no subscriptions and sends no email', async () => { isUserOnlineMock = jest.fn().mockReturnValue(false) await chatReceiver.relateTo(chatSender, 'blocked') @@ -825,13 +863,37 @@ describe('notifications', () => { }, }) + expect(pubsubSpy).not.toHaveBeenCalled() + expect(pubsubSpy).not.toHaveBeenCalled() + + expect(sendMailMock).not.toHaveBeenCalled() + expect(chatMessageTemplateMock).not.toHaveBeenCalled() + }) + }) + + describe('if the chatReceiver has muted chatSender', () => { + it('publishes no subscriptions and sends no email', async () => { + isUserOnlineMock = jest.fn().mockReturnValue(false) + await chatReceiver.relateTo(chatSender, 'muted') + + await mutate({ + mutation: createMessageMutation(), + variables: { + roomId, + content: 'Some nice message to chatReceiver', + }, + }) + + expect(pubsubSpy).not.toHaveBeenCalled() + expect(pubsubSpy).not.toHaveBeenCalled() + expect(sendMailMock).not.toHaveBeenCalled() expect(chatMessageTemplateMock).not.toHaveBeenCalled() }) }) describe('if the chatReceiver has disabled `emailNotificationsChatMessage`', () => { - it('sends no email', async () => { + it('publishes subscriptions but sends no email', async () => { isUserOnlineMock = jest.fn().mockReturnValue(false) await chatReceiver.update({ emailNotificationsChatMessage: false }) @@ -843,6 +905,25 @@ describe('notifications', () => { }, }) + expect(pubsubSpy).toHaveBeenCalledWith('ROOM_COUNT_UPDATED', { + roomCountUpdated: '1', + userId: 'chatReceiver', + }) + expect(pubsubSpy).toHaveBeenCalledWith('CHAT_MESSAGE_ADDED', { + chatMessageAdded: expect.objectContaining({ + id: expect.any(String), + content: 'Some nice message to chatReceiver', + senderId: 'chatSender', + username: 'chatSender', + avatar: null, + date: expect.any(String), + saved: true, + distributed: false, + seen: false, + }), + userId: 'chatReceiver', + }) + expect(sendMailMock).not.toHaveBeenCalled() expect(chatMessageTemplateMock).not.toHaveBeenCalled() }) diff --git a/backend/src/middleware/notifications/notificationsMiddleware.ts b/backend/src/middleware/notifications/notificationsMiddleware.ts index ebbcd7886..62050b3cc 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.ts @@ -7,7 +7,9 @@ import { import { isUserOnline } from '@middleware/helpers/isUserOnline' import { validateNotifyUsers } from '@middleware/validation/validationMiddleware' // eslint-disable-next-line import/no-cycle -import { pubsub, NOTIFICATION_ADDED } from '@src/server' +import { getUnreadRoomsCount } from '@schema/resolvers/rooms' +// eslint-disable-next-line import/no-cycle +import { pubsub, NOTIFICATION_ADDED, ROOM_COUNT_UPDATED, CHAT_MESSAGE_ADDED } from '@src/server' import extractMentionedUsers from './mentions/extractMentionedUsers' @@ -436,7 +438,7 @@ const notifyUsersOfComment = async (label, commentId, reason, context) => { const handleCreateMessage = async (resolve, root, args, context, resolveInfo) => { // Execute resolver - const result = await resolve(root, args, context, resolveInfo) + const message = await resolve(root, args, context, resolveInfo) // Query Parameters const { roomId } = args @@ -452,7 +454,7 @@ const handleCreateMessage = async (resolve, root, args, context, resolveInfo) => MATCH (room)<-[:CHATS_IN]-(recipientUser:User)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress) WHERE NOT recipientUser.id = $currentUserId AND NOT (recipientUser)-[:BLOCKED]-(senderUser) - AND NOT recipientUser.emailNotificationsChatMessage = false + AND NOT (recipientUser)-[:MUTED]->(senderUser) RETURN senderUser {.*}, recipientUser {.*}, emailAddress {.email} ` const txResponse = await transaction.run(messageRecipientCypher, { @@ -471,13 +473,27 @@ const handleCreateMessage = async (resolve, root, args, context, resolveInfo) => // Execute Query const { senderUser, recipientUser, email } = await messageRecipient - // Send EMail if we found a user(not blocked) and he is not considered online - if (recipientUser && !isUserOnline(recipientUser)) { - void sendMail(chatMessageTemplate({ email, variables: { senderUser, recipientUser } })) + if (recipientUser) { + // send subscriptions + const roomCountUpdated = await getUnreadRoomsCount(recipientUser.id, session) + + void pubsub.publish(ROOM_COUNT_UPDATED, { + roomCountUpdated, + userId: recipientUser.id, + }) + void pubsub.publish(CHAT_MESSAGE_ADDED, { + chatMessageAdded: message, + userId: recipientUser.id, + }) + + // Send EMail if we found a user(not blocked) and he is not considered online + if (recipientUser.emailNotificationsChatMessage !== false && !isUserOnline(recipientUser)) { + void sendMail(chatMessageTemplate({ email, variables: { senderUser, recipientUser } })) + } } // Return resolver result to client - return result + return message } catch (error) { throw new Error(error) } finally { diff --git a/backend/src/schema/resolvers/messages.spec.ts b/backend/src/schema/resolvers/messages.spec.ts index 4384ddd0f..83d4134cb 100644 --- a/backend/src/schema/resolvers/messages.spec.ts +++ b/backend/src/schema/resolvers/messages.spec.ts @@ -9,13 +9,13 @@ import createServer, { pubsub } from '@src/server' const driver = getDriver() const neode = getNeode() -const pubsubSpy = jest.spyOn(pubsub, 'publish') - let query let mutate let authenticatedUser let chattingUser, otherChattingUser, notChattingUser +const pubsubSpy = jest.spyOn(pubsub, 'publish') + beforeAll(async () => { await cleanDatabase() @@ -118,7 +118,7 @@ describe('Message', () => { }) describe('user chats in room', () => { - it('returns the message and publishes subscriptions', async () => { + it('returns the message', async () => { await expect( mutate({ mutation: createMessageMutation(), @@ -143,24 +143,6 @@ describe('Message', () => { }, }, }) - expect(pubsubSpy).toBeCalledWith('ROOM_COUNT_UPDATED', { - roomCountUpdated: '1', - userId: 'other-chatting-user', - }) - expect(pubsubSpy).toBeCalledWith('CHAT_MESSAGE_ADDED', { - chatMessageAdded: expect.objectContaining({ - id: expect.any(String), - content: 'Some nice message to other chatting user', - senderId: 'chatting-user', - username: 'Chatting User', - avatar: expect.any(String), - date: expect.any(String), - saved: true, - distributed: false, - seen: false, - }), - userId: 'other-chatting-user', - }) }) describe('room is updated as well', () => { diff --git a/backend/src/schema/resolvers/messages.ts b/backend/src/schema/resolvers/messages.ts index 6879c4be9..1d59b4bbd 100644 --- a/backend/src/schema/resolvers/messages.ts +++ b/backend/src/schema/resolvers/messages.ts @@ -1,10 +1,9 @@ import { withFilter } from 'graphql-subscriptions' import { neo4jgraphql } from 'neo4j-graphql-js' -import { pubsub, ROOM_COUNT_UPDATED, CHAT_MESSAGE_ADDED } from '@src/server' +import { pubsub, CHAT_MESSAGE_ADDED } from '@src/server' import Resolver from './helpers/Resolver' -import { getUnreadRoomsCount } from './rooms' const setMessagesAsDistributed = async (undistributedMessagesIds, session) => { return session.writeTransaction(async (transaction) => { @@ -111,22 +110,7 @@ export default { return message }) try { - const message = await writeTxResultPromise - if (message) { - const roomCountUpdated = await getUnreadRoomsCount(message.recipientId, session) - - // send subscriptions - void pubsub.publish(ROOM_COUNT_UPDATED, { - roomCountUpdated, - userId: message.recipientId, - }) - void pubsub.publish(CHAT_MESSAGE_ADDED, { - chatMessageAdded: message, - userId: message.recipientId, - }) - } - - return message + return await writeTxResultPromise } catch (error) { throw new Error(error) } finally { diff --git a/backend/src/schema/resolvers/rooms.spec.ts b/backend/src/schema/resolvers/rooms.spec.ts index 87ebb4557..2025681ad 100644 --- a/backend/src/schema/resolvers/rooms.spec.ts +++ b/backend/src/schema/resolvers/rooms.spec.ts @@ -387,6 +387,34 @@ describe('Room', () => { }, }) }) + + it('when chattingUser is blocked has 0 unread rooms', async () => { + authenticatedUser = await otherChattingUser.toJson() + await otherChattingUser.relateTo(chattingUser, 'blocked') + await expect( + query({ + query: unreadRoomsQuery(), + }), + ).resolves.toMatchObject({ + data: { + UnreadRooms: 0, + }, + }) + }) + + it('when chattingUser is muted has 0 unread rooms', async () => { + authenticatedUser = await otherChattingUser.toJson() + await otherChattingUser.relateTo(chattingUser, 'muted') + await expect( + query({ + query: unreadRoomsQuery(), + }), + ).resolves.toMatchObject({ + data: { + UnreadRooms: 0, + }, + }) + }) }) describe('as not chatting user', () => { diff --git a/backend/src/schema/resolvers/rooms.ts b/backend/src/schema/resolvers/rooms.ts index 0ff37b594..1bfa354e9 100644 --- a/backend/src/schema/resolvers/rooms.ts +++ b/backend/src/schema/resolvers/rooms.ts @@ -1,6 +1,7 @@ import { withFilter } from 'graphql-subscriptions' import { neo4jgraphql } from 'neo4j-graphql-js' +// eslint-disable-next-line import/no-cycle import { pubsub, ROOM_COUNT_UPDATED } from '@src/server' import Resolver from './helpers/Resolver' @@ -8,8 +9,10 @@ import Resolver from './helpers/Resolver' export const getUnreadRoomsCount = async (userId, session) => { return session.readTransaction(async (transaction) => { const unreadRoomsCypher = ` - MATCH (:User { id: $userId })-[:CHATS_IN]->(room:Room)<-[:INSIDE]-(message:Message)<-[:CREATED]-(sender:User) + MATCH (user:User { id: $userId })-[:CHATS_IN]->(room:Room)<-[:INSIDE]-(message:Message)<-[:CREATED]-(sender:User) WHERE NOT sender.id = $userId AND NOT message.seen + AND NOT (user)-[:BLOCKED]->(sender) + AND NOT (user)-[:MUTED]->(sender) RETURN toString(COUNT(DISTINCT room)) AS count ` const unreadRoomsTxResponse = await transaction.run(unreadRoomsCypher, { userId }) From 4bd42009fd698eff5293a5ff75a6069e324ddf40 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Tue, 15 Apr 2025 18:17:13 +0200 Subject: [PATCH 02/54] refactor(backend): fix is muted by me query (#8365) * use EXISTS for isMutedByMe * corrected query in gql --------- Co-authored-by: mahula --- backend/src/schema/resolvers/groups.ts | 2 +- backend/src/schema/types/type/Group.gql | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/backend/src/schema/resolvers/groups.ts b/backend/src/schema/resolvers/groups.ts index 4bf535f35..f1befc67b 100644 --- a/backend/src/schema/resolvers/groups.ts +++ b/backend/src/schema/resolvers/groups.ts @@ -440,7 +440,7 @@ export default { }, boolean: { isMutedByMe: - 'MATCH (this)<-[:MUTED]-(related:User {id: $cypherParams.currentUserId}) RETURN COUNT(related) >= 1', + 'MATCH (this) RETURN EXISTS( (this)<-[:MUTED]-(:User {id: $cypherParams.currentUserId}) )', }, }), }, diff --git a/backend/src/schema/types/type/Group.gql b/backend/src/schema/types/type/Group.gql index 0d399d287..9bcac5047 100644 --- a/backend/src/schema/types/type/Group.gql +++ b/backend/src/schema/types/type/Group.gql @@ -42,9 +42,7 @@ type Group { posts: [Post] @relation(name: "IN", direction: "IN") - isMutedByMe: Boolean! - @cypher( - statement: "MATCH (this)<-[m:MUTED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(m) >= 1") + isMutedByMe: Boolean! @cypher(statement: "MATCH (this) RETURN EXISTS( (this)<-[:MUTED]-(:User {id: $cypherParams.currentUserId}) )") } From 3ff328ff2f46640c2c2deeff586b72dbb4801a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 16 Apr 2025 02:18:03 +0200 Subject: [PATCH 03/54] chore(frontend): add '.nvmrc' file to new frontend (#7112) * Add '.nvmrc' file to new frontend * Increase node version to 'v20.12.1' in frontend --- frontend/.nvmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 frontend/.nvmrc diff --git a/frontend/.nvmrc b/frontend/.nvmrc new file mode 100644 index 000000000..5871e601c --- /dev/null +++ b/frontend/.nvmrc @@ -0,0 +1 @@ +v20.12.1 \ No newline at end of file From 5d154bcb85b6f409bb751169fb51f6e08cdfc77b Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Wed, 16 Apr 2025 11:08:30 +0200 Subject: [PATCH 04/54] fix migrations (#8390) async migrations should not call next() Co-authored-by: mahula --- backend/src/db/migrate/template.ts | 2 -- backend/src/db/migrations/1613589876420-null_mutation.ts | 8 ++------ .../migrations/1614023644903-add-clickedCount-to-posts.ts | 2 -- .../1614177130817-add-viewedTeaserCount-to-posts.ts | 2 -- .../db/migrations/20210506150512-add-donations-node.ts | 2 -- ...39-add-sendNotificationEmails-property-to-all-users.ts | 2 -- ...-create_fulltext_indices_and_unique_keys_for_groups.ts | 2 -- .../migrations/20230320130345-fulltext-search-indexes.ts | 2 -- .../migrations/20230329150329-article-label-for-posts.ts | 2 -- .../db/migrations/20230608130637-add-postType-property.ts | 2 -- .../src/db/migrations/20231017141022-fix-event-dates.ts | 2 -- .../migrations/20250331130323-author-observes-own-post.ts | 2 -- .../migrations/20250331140313-commenter-observes-post.ts | 2 -- .../20250405030454-email-notification-settings.ts | 1 - 14 files changed, 2 insertions(+), 31 deletions(-) diff --git a/backend/src/db/migrate/template.ts b/backend/src/db/migrate/template.ts index f9eb1a338..569f274bf 100644 --- a/backend/src/db/migrate/template.ts +++ b/backend/src/db/migrate/template.ts @@ -11,7 +11,6 @@ export async function up(next) { // Implement your migration here. await transaction.run(``) await transaction.commit() - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) @@ -33,7 +32,6 @@ export async function down(next) { // Implement your migration here. await transaction.run(``) await transaction.commit() - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) diff --git a/backend/src/db/migrations/1613589876420-null_mutation.ts b/backend/src/db/migrations/1613589876420-null_mutation.ts index 8efe667be..ddd5bb14b 100644 --- a/backend/src/db/migrations/1613589876420-null_mutation.ts +++ b/backend/src/db/migrations/1613589876420-null_mutation.ts @@ -1,9 +1,5 @@ 'use strict' -export async function up(next) { - next() -} +export async function up(next) {} -export async function down(next) { - next() -} +export async function down(next) {} diff --git a/backend/src/db/migrations/1614023644903-add-clickedCount-to-posts.ts b/backend/src/db/migrations/1614023644903-add-clickedCount-to-posts.ts index ce3515ac7..25b0addaf 100644 --- a/backend/src/db/migrations/1614023644903-add-clickedCount-to-posts.ts +++ b/backend/src/db/migrations/1614023644903-add-clickedCount-to-posts.ts @@ -15,7 +15,6 @@ export async function up(next) { SET p.clickedCount = 0 `) await transaction.commit() - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) @@ -39,7 +38,6 @@ export async function down(next) { REMOVE p.clickedCount `) await transaction.commit() - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) diff --git a/backend/src/db/migrations/1614177130817-add-viewedTeaserCount-to-posts.ts b/backend/src/db/migrations/1614177130817-add-viewedTeaserCount-to-posts.ts index 5615aa4e0..8073d8e22 100644 --- a/backend/src/db/migrations/1614177130817-add-viewedTeaserCount-to-posts.ts +++ b/backend/src/db/migrations/1614177130817-add-viewedTeaserCount-to-posts.ts @@ -15,7 +15,6 @@ export async function up(next) { SET p.viewedTeaserCount = 0 `) await transaction.commit() - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) @@ -39,7 +38,6 @@ export async function down(next) { REMOVE p.viewedTeaserCount `) await transaction.commit() - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) diff --git a/backend/src/db/migrations/20210506150512-add-donations-node.ts b/backend/src/db/migrations/20210506150512-add-donations-node.ts index 3d01f28bb..073d4f348 100644 --- a/backend/src/db/migrations/20210506150512-add-donations-node.ts +++ b/backend/src/db/migrations/20210506150512-add-donations-node.ts @@ -27,7 +27,6 @@ export async function up(next) { { donationId }, ) await transaction.commit() - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) @@ -53,7 +52,6 @@ export async function down(next) { RETURN donationInfo `) await transaction.commit() - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) diff --git a/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.ts b/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.ts index bd886db02..be0eb8dd6 100644 --- a/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.ts +++ b/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.ts @@ -17,7 +17,6 @@ export async function up(next) { `, ) await transaction.commit() - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) @@ -45,7 +44,6 @@ export async function down(next) { `, ) await transaction.commit() - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) diff --git a/backend/src/db/migrations/20220803060819-create_fulltext_indices_and_unique_keys_for_groups.ts b/backend/src/db/migrations/20220803060819-create_fulltext_indices_and_unique_keys_for_groups.ts index c53edb9a0..a3f220429 100644 --- a/backend/src/db/migrations/20220803060819-create_fulltext_indices_and_unique_keys_for_groups.ts +++ b/backend/src/db/migrations/20220803060819-create_fulltext_indices_and_unique_keys_for_groups.ts @@ -26,7 +26,6 @@ export async function up(next) { `) */ await transaction.commit() - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) @@ -59,7 +58,6 @@ export async function down(next) { `) await transaction.commit() */ - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) diff --git a/backend/src/db/migrations/20230320130345-fulltext-search-indexes.ts b/backend/src/db/migrations/20230320130345-fulltext-search-indexes.ts index 765042aad..af5cf2d8c 100644 --- a/backend/src/db/migrations/20230320130345-fulltext-search-indexes.ts +++ b/backend/src/db/migrations/20230320130345-fulltext-search-indexes.ts @@ -34,7 +34,6 @@ export async function up(next) { ) await transaction.commit() */ - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) @@ -60,7 +59,6 @@ export async function down(next) { await transaction.run(`CALL db.index.fulltext.drop("tag_fulltext_search")`) await transaction.commit() */ - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) diff --git a/backend/src/db/migrations/20230329150329-article-label-for-posts.ts b/backend/src/db/migrations/20230329150329-article-label-for-posts.ts index f33aa818a..9f211e60e 100644 --- a/backend/src/db/migrations/20230329150329-article-label-for-posts.ts +++ b/backend/src/db/migrations/20230329150329-article-label-for-posts.ts @@ -14,7 +14,6 @@ export async function up(next) { RETURN post `) await transaction.commit() - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) @@ -39,7 +38,6 @@ export async function down(next) { RETURN post `) await transaction.commit() - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) diff --git a/backend/src/db/migrations/20230608130637-add-postType-property.ts b/backend/src/db/migrations/20230608130637-add-postType-property.ts index 26c99ce48..02101b620 100644 --- a/backend/src/db/migrations/20230608130637-add-postType-property.ts +++ b/backend/src/db/migrations/20230608130637-add-postType-property.ts @@ -14,7 +14,6 @@ export async function up(next) { RETURN post `) await transaction.commit() - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) @@ -39,7 +38,6 @@ export async function down(next) { RETURN post `) await transaction.commit() - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) diff --git a/backend/src/db/migrations/20231017141022-fix-event-dates.ts b/backend/src/db/migrations/20231017141022-fix-event-dates.ts index b2edf17dc..1e80dfff4 100644 --- a/backend/src/db/migrations/20231017141022-fix-event-dates.ts +++ b/backend/src/db/migrations/20231017141022-fix-event-dates.ts @@ -34,7 +34,6 @@ export async function up(next) { `) } await transaction.commit() - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) @@ -54,7 +53,6 @@ export async function down(next) { try { // No sense in running this down - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) diff --git a/backend/src/db/migrations/20250331130323-author-observes-own-post.ts b/backend/src/db/migrations/20250331130323-author-observes-own-post.ts index 619b5f1fa..a32539f40 100644 --- a/backend/src/db/migrations/20250331130323-author-observes-own-post.ts +++ b/backend/src/db/migrations/20250331130323-author-observes-own-post.ts @@ -21,7 +21,6 @@ export async function up(next) { RETURN post `) await transaction.commit() - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) @@ -47,7 +46,6 @@ export async function down(next) { RETURN p `) await transaction.commit() - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) diff --git a/backend/src/db/migrations/20250331140313-commenter-observes-post.ts b/backend/src/db/migrations/20250331140313-commenter-observes-post.ts index cc9a82160..be55a6b64 100644 --- a/backend/src/db/migrations/20250331140313-commenter-observes-post.ts +++ b/backend/src/db/migrations/20250331140313-commenter-observes-post.ts @@ -21,7 +21,6 @@ export async function up(next) { RETURN post `) await transaction.commit() - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) @@ -48,7 +47,6 @@ export async function down(next) { RETURN p `) await transaction.commit() - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) diff --git a/backend/src/db/migrations/20250405030454-email-notification-settings.ts b/backend/src/db/migrations/20250405030454-email-notification-settings.ts index 8b02e866a..abe9c658d 100644 --- a/backend/src/db/migrations/20250405030454-email-notification-settings.ts +++ b/backend/src/db/migrations/20250405030454-email-notification-settings.ts @@ -22,7 +22,6 @@ export async function up(next) { REMOVE user.sendNotificationEmails `) await transaction.commit() - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) From 5e6628e030fe578711b315ec16c8f6c45fe64f4b Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 16 Apr 2025 14:54:44 +0200 Subject: [PATCH 05/54] Add .nuxt to gitignore (#8393) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d9d081e31..6f71f582b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ yarn-error.log* kubeconfig.yaml backup-cron-job.log .vscode +.nuxt node_modules/ cypress/videos From 9440ad5cc36d1f1c52f2255e8d5e5b62477c9320 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 16 Apr 2025 17:34:29 +0200 Subject: [PATCH 06/54] feat(backend): no notification mails to users online (#8397) --- ...ificationsMiddleware.online-status.spec.ts | 146 ++++++++++++++++++ .../notifications/notificationsMiddleware.ts | 5 +- 2 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 backend/src/middleware/notifications/notificationsMiddleware.online-status.spec.ts diff --git a/backend/src/middleware/notifications/notificationsMiddleware.online-status.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.online-status.spec.ts new file mode 100644 index 000000000..acc446d6b --- /dev/null +++ b/backend/src/middleware/notifications/notificationsMiddleware.online-status.spec.ts @@ -0,0 +1,146 @@ +import { createTestClient } from 'apollo-server-testing' +import gql from 'graphql-tag' + +import Factory, { cleanDatabase } from '@db/factories' +import { getNeode, getDriver } from '@db/neo4j' +import CONFIG from '@src/config' +import createServer from '@src/server' + +CONFIG.CATEGORIES_ACTIVE = false + +const sendMailMock = jest.fn() +jest.mock('../helpers/email/sendMail', () => ({ + sendMail: () => sendMailMock(), +})) + +let isUserOnlineMock = jest.fn().mockReturnValue(false) +jest.mock('../helpers/isUserOnline', () => ({ + isUserOnline: () => isUserOnlineMock(), +})) + +let server, mutate, authenticatedUser + +let postAuthor + +const driver = getDriver() +const neode = getNeode() + +const createPostMutation = gql` + mutation ($id: ID, $title: String!, $content: String!, $groupId: ID) { + CreatePost(id: $id, title: $title, content: $content, groupId: $groupId) { + id + title + content + } + } +` + +beforeAll(async () => { + await cleanDatabase() + + const createServerResult = createServer({ + context: () => { + return { + user: authenticatedUser, + neode, + driver, + cypherParams: { + currentUserId: authenticatedUser ? authenticatedUser.id : null, + }, + } + }, + }) + server = createServerResult.server + const createTestClientResult = createTestClient(server) + mutate = createTestClientResult.mutate +}) + +afterAll(async () => { + await cleanDatabase() + driver.close() +}) + +afterEach(async () => { + await cleanDatabase() +}) + +describe('online status and sending emails', () => { + beforeEach(async () => { + postAuthor = await Factory.build( + 'user', + { + id: 'post-author', + name: 'Post Author', + slug: 'post-author', + }, + { + email: 'test@example.org', + password: '1234', + }, + ) + await Factory.build( + 'user', + { + id: 'other-user', + name: 'Other User', + slug: 'other-user', + }, + { + email: 'test2@example.org', + password: '1234', + }, + ) + }) + + describe('user is online', () => { + beforeAll(() => { + isUserOnlineMock = jest.fn().mockReturnValue(true) + }) + + describe('mentioned in post', () => { + beforeEach(async () => { + jest.clearAllMocks() + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createPostMutation, + variables: { + id: 'post-online-1', + title: 'This post mentions the other user', + content: + 'Hello @other-user, are you fine?', + }, + }) + }) + + it('sends NO email to the other user', () => { + expect(sendMailMock).not.toBeCalled() + }) + }) + }) + + describe('user is offline', () => { + beforeAll(() => { + isUserOnlineMock = jest.fn().mockReturnValue(false) + }) + + describe('mentioned in post', () => { + beforeEach(async () => { + jest.clearAllMocks() + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createPostMutation, + variables: { + id: 'post-offline-1', + title: 'This post mentions the other user', + content: + 'Hello @other-user, are you fine?', + }, + }) + }) + + it('sends email to the other user', () => { + expect(sendMailMock).toBeCalledTimes(1) + }) + }) + }) +}) diff --git a/backend/src/middleware/notifications/notificationsMiddleware.ts b/backend/src/middleware/notifications/notificationsMiddleware.ts index 62050b3cc..f70e9fc89 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.ts @@ -49,7 +49,10 @@ const publishNotifications = async (context, promises, emailNotificationSetting: ) notifications.forEach((notificationAdded, index) => { pubsub.publish(NOTIFICATION_ADDED, { notificationAdded }) - if (notificationAdded.to[emailNotificationSetting] ?? true) { + if ( + (notificationAdded.to[emailNotificationSetting] ?? true) && + !isUserOnline(notificationAdded.to) + ) { sendMail( notificationTemplate({ email: notificationsEmailAddresses[index].email, From 727713ac1d3298cd5fef8f2c6d86f07ea0f2d6ba Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 16 Apr 2025 18:04:31 +0200 Subject: [PATCH 07/54] mentiioned users in posts and comments of groups (#8392) --- ...icationsMiddleware.followed-users.spec.ts} | 0 ...tionsMiddleware.mentions-in-groups.spec.ts | 789 ++++++++++++++++++ ...cationsMiddleware.observing-posts.spec.ts} | 0 ...cationsMiddleware.posts-in-groups.spec.ts} | 0 .../notifications/notificationsMiddleware.ts | 16 +- 5 files changed, 799 insertions(+), 6 deletions(-) rename backend/src/middleware/notifications/{followed-users.spec.ts => notificationsMiddleware.followed-users.spec.ts} (100%) create mode 100644 backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts rename backend/src/middleware/notifications/{observing-posts.spec.ts => notificationsMiddleware.observing-posts.spec.ts} (100%) rename backend/src/middleware/notifications/{posts-in-groups.spec.ts => notificationsMiddleware.posts-in-groups.spec.ts} (100%) diff --git a/backend/src/middleware/notifications/followed-users.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.followed-users.spec.ts similarity index 100% rename from backend/src/middleware/notifications/followed-users.spec.ts rename to backend/src/middleware/notifications/notificationsMiddleware.followed-users.spec.ts diff --git a/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts new file mode 100644 index 000000000..575a2e86d --- /dev/null +++ b/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts @@ -0,0 +1,789 @@ +import { createTestClient } from 'apollo-server-testing' +import gql from 'graphql-tag' + +import Factory, { cleanDatabase } from '@db/factories' +import { getNeode, getDriver } from '@db/neo4j' +import { + createGroupMutation, + joinGroupMutation, + changeGroupMemberRoleMutation, +} from '@graphql/groups' +import CONFIG from '@src/config' +import createServer from '@src/server' + +CONFIG.CATEGORIES_ACTIVE = false + +const sendMailMock = jest.fn() +jest.mock('../helpers/email/sendMail', () => ({ + sendMail: () => sendMailMock(), +})) + +let server, query, mutate, authenticatedUser + +let postAuthor, groupMember, pendingMember, noMember + +const driver = getDriver() +const neode = getNeode() + +const mentionString = ` + @no-meber + @pending-member + @group-member. +` + +const createPostMutation = gql` + mutation ($id: ID, $title: String!, $content: String!, $groupId: ID) { + CreatePost(id: $id, title: $title, content: $content, groupId: $groupId) { + id + title + content + } + } +` + +const createCommentMutation = gql` + mutation ($id: ID, $postId: ID!, $commentContent: String!) { + CreateComment(id: $id, postId: $postId, content: $commentContent) { + id + content + } + } +` + +const notificationQuery = gql` + query ($read: Boolean) { + notifications(read: $read, orderBy: updatedAt_desc) { + read + reason + createdAt + relatedUser { + id + } + from { + __typename + ... on Post { + id + content + } + ... on Comment { + id + content + } + ... on Group { + id + } + } + } + } +` + +const markAllAsRead = async () => + mutate({ + mutation: gql` + mutation { + markAllAsRead { + id + } + } + `, + }) + +beforeAll(async () => { + await cleanDatabase() + + const createServerResult = createServer({ + context: () => { + return { + user: authenticatedUser, + neode, + driver, + cypherParams: { + currentUserId: authenticatedUser ? authenticatedUser.id : null, + }, + } + }, + }) + server = createServerResult.server + const createTestClientResult = createTestClient(server) + query = createTestClientResult.query + mutate = createTestClientResult.mutate +}) + +afterAll(async () => { + await cleanDatabase() + driver.close() +}) + +describe('mentions in groups', () => { + beforeEach(async () => { + postAuthor = await Factory.build( + 'user', + { + id: 'post-author', + name: 'Post Author', + slug: 'post-author', + }, + { + email: 'test@example.org', + password: '1234', + }, + ) + groupMember = await Factory.build( + 'user', + { + id: 'group-member', + name: 'Group Member', + slug: 'group-member', + }, + { + email: 'test2@example.org', + password: '1234', + }, + ) + pendingMember = await Factory.build( + 'user', + { + id: 'pending-member', + name: 'Pending Member', + slug: 'pending-member', + }, + { + email: 'test3@example.org', + password: '1234', + }, + ) + noMember = await Factory.build( + 'user', + { + id: 'no-member', + name: 'No Member', + slug: 'no-member', + }, + { + email: 'test4@example.org', + password: '1234', + }, + ) + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createGroupMutation(), + variables: { + id: 'public-group', + name: 'A public group', + description: 'A public group to test the notifications of mentions', + groupType: 'public', + actionRadius: 'national', + }, + }) + await mutate({ + mutation: createGroupMutation(), + variables: { + id: 'closed-group', + name: 'A closed group', + description: 'A closed group to test the notifications of mentions', + groupType: 'closed', + actionRadius: 'national', + }, + }) + await mutate({ + mutation: createGroupMutation(), + variables: { + id: 'hidden-group', + name: 'A hidden group', + description: 'A hidden group to test the notifications of mentions', + groupType: 'hidden', + actionRadius: 'national', + }, + }) + authenticatedUser = await groupMember.toJson() + await mutate({ + mutation: joinGroupMutation(), + variables: { + groupId: 'public-group', + userId: 'group-member', + }, + }) + await mutate({ + mutation: joinGroupMutation(), + variables: { + groupId: 'closed-group', + userId: 'group-member', + }, + }) + await mutate({ + mutation: joinGroupMutation(), + variables: { + groupId: 'hidden-group', + userId: 'group-member', + }, + }) + authenticatedUser = await pendingMember.toJson() + await mutate({ + mutation: joinGroupMutation(), + variables: { + groupId: 'public-group', + userId: 'pending-member', + }, + }) + await mutate({ + mutation: joinGroupMutation(), + variables: { + groupId: 'closed-group', + userId: 'pending-member', + }, + }) + await mutate({ + mutation: joinGroupMutation(), + variables: { + groupId: 'hidden-group', + userId: 'pending-member', + }, + }) + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: changeGroupMemberRoleMutation(), + variables: { + groupId: 'closed-group', + userId: 'group-member', + roleInGroup: 'usual', + }, + }) + await mutate({ + mutation: changeGroupMemberRoleMutation(), + variables: { + groupId: 'hidden-group', + userId: 'group-member', + roleInGroup: 'usual', + }, + }) + authenticatedUser = await groupMember.toJson() + await markAllAsRead() + }) + + afterEach(async () => { + await cleanDatabase() + }) + + describe('post in public group', () => { + beforeEach(async () => { + jest.clearAllMocks() + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createPostMutation, + variables: { + id: 'public-post', + title: 'This is the post in the public group', + content: `Hey ${mentionString}! Please read this`, + groupId: 'public-group', + }, + }) + }) + + it('sends a notification to the no member', async () => { + authenticatedUser = await noMember.toJson() + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toMatchObject({ + data: { + notifications: [ + { + from: { + __typename: 'Post', + id: 'public-post', + }, + read: false, + reason: 'mentioned_in_post', + }, + ], + }, + errors: undefined, + }) + }) + + it('sends a notification to the group member', async () => { + authenticatedUser = await groupMember.toJson() + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toMatchObject({ + data: { + notifications: [ + { + from: { + __typename: 'Post', + id: 'public-post', + }, + read: false, + reason: 'post_in_group', + }, + { + from: { + __typename: 'Post', + id: 'public-post', + }, + read: false, + reason: 'mentioned_in_post', + }, + ], + }, + errors: undefined, + }) + }) + + it('sends 3 emails, 2 mentions and 1 post in group', () => { + expect(sendMailMock).toHaveBeenCalledTimes(5) + }) + }) + + describe('post in closed group', () => { + beforeEach(async () => { + jest.clearAllMocks() + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createPostMutation, + variables: { + id: 'closed-post', + title: 'This is the post in the closed group', + content: `Hey members ${mentionString}! Please read this`, + groupId: 'closed-group', + }, + }) + }) + + it('sends NO notification to the no member', async () => { + authenticatedUser = await noMember.toJson() + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toMatchObject({ + data: { + notifications: [], + }, + errors: undefined, + }) + }) + + it('sends NO notification to the pending member', async () => { + authenticatedUser = await pendingMember.toJson() + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toMatchObject({ + data: { + notifications: [], + }, + errors: undefined, + }) + }) + + it('sends a notification to the group member', async () => { + authenticatedUser = await groupMember.toJson() + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toMatchObject({ + data: { + notifications: [ + { + from: { + __typename: 'Post', + id: 'closed-post', + }, + read: false, + reason: 'post_in_group', + }, + { + from: { + __typename: 'Post', + id: 'closed-post', + }, + read: false, + reason: 'mentioned_in_post', + }, + ], + }, + errors: undefined, + }) + }) + + it('sends 2 emails, one mention and one post in group', () => { + expect(sendMailMock).toHaveBeenCalledTimes(2) + }) + }) + + describe('post in hidden group', () => { + beforeEach(async () => { + jest.clearAllMocks() + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createPostMutation, + variables: { + id: 'hidden-post', + title: 'This is the post in the hidden group', + content: `Hey hiders ${mentionString}! Please read this`, + groupId: 'hidden-group', + }, + }) + }) + + it('sends NO notification to the no member', async () => { + authenticatedUser = await noMember.toJson() + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toMatchObject({ + data: { + notifications: [], + }, + errors: undefined, + }) + }) + + it('sends NO notification to the pending member', async () => { + authenticatedUser = await pendingMember.toJson() + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toMatchObject({ + data: { + notifications: [], + }, + errors: undefined, + }) + }) + + it('sends a notification to the group member', async () => { + authenticatedUser = await groupMember.toJson() + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toMatchObject({ + data: { + notifications: [ + { + from: { + __typename: 'Post', + id: 'hidden-post', + }, + read: false, + reason: 'post_in_group', + }, + { + from: { + __typename: 'Post', + id: 'hidden-post', + }, + read: false, + reason: 'mentioned_in_post', + }, + ], + }, + errors: undefined, + }) + }) + + it('sends 2 emails, one mention and one post in group', () => { + expect(sendMailMock).toHaveBeenCalledTimes(2) + }) + }) + + describe('comments on group posts', () => { + describe('public group', () => { + beforeEach(async () => { + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createPostMutation, + variables: { + id: 'public-post', + title: 'This is the post in the public group', + content: `Some public content`, + groupId: 'public-group', + }, + }) + authenticatedUser = await groupMember.toJson() + await markAllAsRead() + authenticatedUser = await postAuthor.toJson() + jest.clearAllMocks() + await mutate({ + mutation: createCommentMutation, + variables: { + id: 'public-comment', + postId: 'public-post', + commentContent: `Hey everyone ${mentionString}! Please read this`, + }, + }) + }) + + it('sends a notification to the no member', async () => { + authenticatedUser = await noMember.toJson() + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toMatchObject({ + data: { + notifications: [ + { + from: { + __typename: 'Comment', + id: 'public-comment', + }, + read: false, + reason: 'mentioned_in_comment', + }, + ], + }, + errors: undefined, + }) + }) + + it('sends a notification to the group member', async () => { + authenticatedUser = await groupMember.toJson() + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toMatchObject({ + data: { + notifications: [ + { + from: { + __typename: 'Comment', + id: 'public-comment', + }, + read: false, + reason: 'mentioned_in_comment', + }, + ], + }, + errors: undefined, + }) + }) + + it('sends 2 emails', () => { + expect(sendMailMock).toHaveBeenCalledTimes(3) + }) + }) + + describe('closed group', () => { + beforeEach(async () => { + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createPostMutation, + variables: { + id: 'closed-post', + title: 'This is the post in the closed group', + content: `Some closed content`, + groupId: 'closed-group', + }, + }) + authenticatedUser = await groupMember.toJson() + await markAllAsRead() + authenticatedUser = await postAuthor.toJson() + jest.clearAllMocks() + await mutate({ + mutation: createCommentMutation, + variables: { + id: 'closed-comment', + postId: 'closed-post', + commentContent: `Hey members ${mentionString}! Please read this`, + }, + }) + }) + + it('sends NO notification to the no member', async () => { + authenticatedUser = await noMember.toJson() + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toMatchObject({ + data: { + notifications: [], + }, + errors: undefined, + }) + }) + + it('sends NO notification to the pending member', async () => { + authenticatedUser = await pendingMember.toJson() + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toMatchObject({ + data: { + notifications: [], + }, + errors: undefined, + }) + }) + + it('sends a notification to the group member', async () => { + authenticatedUser = await groupMember.toJson() + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toMatchObject({ + data: { + notifications: [ + { + from: { + __typename: 'Comment', + id: 'closed-comment', + }, + read: false, + reason: 'mentioned_in_comment', + }, + ], + }, + errors: undefined, + }) + }) + + it('sends 1 email', () => { + expect(sendMailMock).toHaveBeenCalledTimes(1) + }) + }) + + describe('hidden group', () => { + beforeEach(async () => { + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createPostMutation, + variables: { + id: 'hidden-post', + title: 'This is the post in the hidden group', + content: `Some hidden content`, + groupId: 'hidden-group', + }, + }) + authenticatedUser = await groupMember.toJson() + await markAllAsRead() + authenticatedUser = await postAuthor.toJson() + jest.clearAllMocks() + await mutate({ + mutation: createCommentMutation, + variables: { + id: 'hidden-comment', + postId: 'hidden-post', + commentContent: `Hey hiders ${mentionString}! Please read this`, + }, + }) + }) + + it('sends NO notification to the no member', async () => { + authenticatedUser = await noMember.toJson() + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toMatchObject({ + data: { + notifications: [], + }, + errors: undefined, + }) + }) + + it('sends NO notification to the pending member', async () => { + authenticatedUser = await pendingMember.toJson() + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toMatchObject({ + data: { + notifications: [], + }, + errors: undefined, + }) + }) + + it('sends a notification to the group member', async () => { + authenticatedUser = await groupMember.toJson() + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toMatchObject({ + data: { + notifications: [ + { + from: { + __typename: 'Comment', + id: 'hidden-comment', + }, + read: false, + reason: 'mentioned_in_comment', + }, + ], + }, + errors: undefined, + }) + }) + + it('sends 1 email', () => { + expect(sendMailMock).toHaveBeenCalledTimes(1) + }) + }) + }) +}) diff --git a/backend/src/middleware/notifications/observing-posts.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.observing-posts.spec.ts similarity index 100% rename from backend/src/middleware/notifications/observing-posts.spec.ts rename to backend/src/middleware/notifications/notificationsMiddleware.observing-posts.spec.ts diff --git a/backend/src/middleware/notifications/posts-in-groups.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts similarity index 100% rename from backend/src/middleware/notifications/posts-in-groups.spec.ts rename to backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts diff --git a/backend/src/middleware/notifications/notificationsMiddleware.ts b/backend/src/middleware/notifications/notificationsMiddleware.ts index f70e9fc89..27216988f 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.ts @@ -349,9 +349,10 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => { case 'mentioned_in_post': { mentionedCypher = ` MATCH (post: Post { id: $id })<-[:WROTE]-(author: User) - MATCH (user: User) - WHERE user.id in $idsOfUsers - AND NOT (user)-[:BLOCKED]-(author) + MATCH (user: User) WHERE user.id in $idsOfUsers AND NOT (user)-[:BLOCKED]-(author) + OPTIONAL MATCH (post)-[:IN]->(group:Group) + OPTIONAL MATCH (group)<-[membership:MEMBER_OF]-(user) + WITH post, author, user, group WHERE group IS NULL OR group.groupType = 'public' OR membership.role IN ['usual', 'admin', 'owner'] MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user) WITH post AS resource, notification, user ` @@ -361,9 +362,12 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => { mentionedCypher = ` MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(commenter: User) MATCH (user: User) - WHERE user.id in $idsOfUsers - AND NOT (user)-[:BLOCKED]-(commenter) - AND NOT (user)-[:BLOCKED]-(postAuthor) + WHERE user.id in $idsOfUsers + AND NOT (user)-[:BLOCKED]-(commenter) + AND NOT (user)-[:BLOCKED]-(postAuthor) + OPTIONAL MATCH (post)-[:IN]->(group:Group) + OPTIONAL MATCH (group)<-[membership:MEMBER_OF]-(user) + WITH comment, user, group WHERE group IS NULL OR group.groupType = 'public' OR membership.role IN ['usual', 'admin', 'owner'] MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user) WITH comment AS resource, notification, user ` From d9c8e3571c622346adebf4219219638215c6c6ac Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 17 Apr 2025 12:38:24 +0200 Subject: [PATCH 08/54] downgrade sass to 1.77.6 (#8399) avoid the deprecation warnings --- webapp/package.json | 2 +- webapp/yarn.lock | 182 +++++++------------------------------------- 2 files changed, 27 insertions(+), 157 deletions(-) diff --git a/webapp/package.json b/webapp/package.json index c56684325..45d1778b0 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -49,7 +49,7 @@ "nuxt": "~2.12.1", "nuxt-dropzone": "^1.0.4", "nuxt-env": "~0.1.0", - "sass": "^1.86.3", + "sass": "1.77.6", "stack-utils": "^2.0.3", "tippy.js": "^4.3.5", "tiptap": "~1.26.6", diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 1c3212c89..b8a8d4876 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -3423,95 +3423,6 @@ resolved "https://registry.yarnpkg.com/@oclif/screen/-/screen-1.0.4.tgz#b740f68609dfae8aa71c3a6cab15d816407ba493" integrity sha512-60CHpq+eqnTxLZQ4PGHYNwUX572hgpMHGPtTWMjdTMsAvlm69lZV/4ly6O3sAYkomo4NggGcomrDpBe34rxUqw== -"@parcel/watcher-android-arm64@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz#e32d3dda6647791ee930556aee206fcd5ea0fb7a" - integrity sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ== - -"@parcel/watcher-darwin-arm64@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz#0d9e680b7e9ec1c8f54944f1b945aa8755afb12f" - integrity sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw== - -"@parcel/watcher-darwin-x64@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz#f9f1d5ce9d5878d344f14ef1856b7a830c59d1bb" - integrity sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA== - -"@parcel/watcher-freebsd-x64@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz#2b77f0c82d19e84ff4c21de6da7f7d096b1a7e82" - integrity sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw== - -"@parcel/watcher-linux-arm-glibc@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz#92ed322c56dbafa3d2545dcf2803334aee131e42" - integrity sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA== - -"@parcel/watcher-linux-arm-musl@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz#cd48e9bfde0cdbbd2ecd9accfc52967e22f849a4" - integrity sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA== - -"@parcel/watcher-linux-arm64-glibc@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz#7b81f6d5a442bb89fbabaf6c13573e94a46feb03" - integrity sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA== - -"@parcel/watcher-linux-arm64-musl@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz#dcb8ff01077cdf59a18d9e0a4dff7a0cfe5fd732" - integrity sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q== - -"@parcel/watcher-linux-x64-glibc@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz#2e254600fda4e32d83942384d1106e1eed84494d" - integrity sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw== - -"@parcel/watcher-linux-x64-musl@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz#01fcea60fedbb3225af808d3f0a7b11229792eef" - integrity sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA== - -"@parcel/watcher-win32-arm64@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz#87cdb16e0783e770197e52fb1dc027bb0c847154" - integrity sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig== - -"@parcel/watcher-win32-ia32@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz#778c39b56da33e045ba21c678c31a9f9d7c6b220" - integrity sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA== - -"@parcel/watcher-win32-x64@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz#33873876d0bbc588aacce38e90d1d7480ce81cb7" - integrity sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw== - -"@parcel/watcher@^2.4.1": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.5.0.tgz#5c88818b12b8de4307a9d3e6dc3e28eba0dfbd10" - integrity sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ== - dependencies: - detect-libc "^1.0.3" - is-glob "^4.0.3" - micromatch "^4.0.5" - node-addon-api "^7.0.0" - optionalDependencies: - "@parcel/watcher-android-arm64" "2.5.0" - "@parcel/watcher-darwin-arm64" "2.5.0" - "@parcel/watcher-darwin-x64" "2.5.0" - "@parcel/watcher-freebsd-x64" "2.5.0" - "@parcel/watcher-linux-arm-glibc" "2.5.0" - "@parcel/watcher-linux-arm-musl" "2.5.0" - "@parcel/watcher-linux-arm64-glibc" "2.5.0" - "@parcel/watcher-linux-arm64-musl" "2.5.0" - "@parcel/watcher-linux-x64-glibc" "2.5.0" - "@parcel/watcher-linux-x64-musl" "2.5.0" - "@parcel/watcher-win32-arm64" "2.5.0" - "@parcel/watcher-win32-ia32" "2.5.0" - "@parcel/watcher-win32-x64" "2.5.0" - "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" @@ -6892,13 +6803,6 @@ braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" -braces@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" @@ -7421,6 +7325,21 @@ cheerio@^1.0.0-rc.2: lodash "^4.15.0" parse5 "^3.0.1" +"chokidar@>=3.0.0 <4.0.0", chokidar@^3.3.1, chokidar@^3.4.1: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" @@ -7440,28 +7359,6 @@ chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" -chokidar@^3.3.1, chokidar@^3.4.1: - version "3.6.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" - integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -chokidar@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" - integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== - dependencies: - readdirp "^4.0.1" - chownr@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" @@ -8817,7 +8714,7 @@ detect-indent@^5.0.0: resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50= -detect-libc@^1.0.2, detect-libc@^1.0.3: +detect-libc@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= @@ -10353,13 +10250,6 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - finalhandler@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" @@ -11812,10 +11702,10 @@ ignore@^5.1.1, ignore@^5.1.4: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf" integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A== -immutable@^5.0.2: - version "5.0.3" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.0.3.tgz#aa037e2313ea7b5d400cd9298fa14e404c933db1" - integrity sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw== +immutable@^4.0.0: + version "4.3.7" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.7.tgz#c70145fc90d89fb02021e65c84eb0226e4e5a381" + integrity sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw== import-cwd@^2.0.0: version "2.1.0" @@ -14353,14 +14243,6 @@ micromatch@^4.0.2, micromatch@^4.0.4: braces "^3.0.2" picomatch "^2.3.1" -micromatch@^4.0.5: - version "4.0.8" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" - integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" @@ -14718,11 +14600,6 @@ no-case@^2.2.0, no-case@^2.3.2: dependencies: lower-case "^1.1.1" -node-addon-api@^7.0.0: - version "7.1.1" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" - integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== - node-ask@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/node-ask/-/node-ask-1.0.1.tgz#caaa1076cc58e0364267a0903e3eadfac158396b" @@ -17246,11 +17123,6 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" -readdirp@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.0.2.tgz#388fccb8b75665da3abffe2d8f8ed59fe74c230a" - integrity sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA== - readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -17866,16 +17738,14 @@ sass-resources-loader@^2.2.1: glob "^7.1.6" loader-utils "^2.0.0" -sass@^1.86.3: - version "1.86.3" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.86.3.tgz#0a0d9ea97cb6665e73f409639f8533ce057464c9" - integrity sha512-iGtg8kus4GrsGLRDLRBRHY9dNVA78ZaS7xr01cWnS7PEMQyFtTqBiyCrfpTYTZXRWM94akzckYjh8oADfFNTzw== +sass@1.77.6: + version "1.77.6" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.77.6.tgz#898845c1348078c2e6d1b64f9ee06b3f8bd489e4" + integrity sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q== dependencies: - chokidar "^4.0.0" - immutable "^5.0.2" + chokidar ">=3.0.0 <4.0.0" + immutable "^4.0.0" source-map-js ">=0.6.2 <2.0.0" - optionalDependencies: - "@parcel/watcher" "^2.4.1" sax@^1.2.4, sax@~1.2.4: version "1.2.4" From a31274c7935e5b5b0ecbef86e2ccf386d5b47de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 17 Apr 2025 17:48:59 +0200 Subject: [PATCH 09/54] refactor(webapp): refactor branding of post ribbons and chat etc. (#8395) * Refactor branding of post ribbons * Refactor chat to be more brandable * Refine avatar menu logout item to have danger color * Change login menu item color to '$text-color-link' --- webapp/assets/_new/styles/export.scss | 8 ++++++++ webapp/assets/_new/styles/tokens.scss | 17 ++++++++++++----- webapp/assets/_new/styles/uses.scss | 4 ++++ webapp/components/AvatarMenu/AvatarMenu.vue | 4 ++-- webapp/components/LoginButton/LoginButton.vue | 2 +- webapp/components/Ribbon/index.vue | 13 +++++++------ .../_new/generic/BaseCard/BaseCard.vue | 2 +- webapp/constants/chat.js | 14 +++++++------- webapp/nuxt.config.js | 1 + 9 files changed, 43 insertions(+), 22 deletions(-) create mode 100644 webapp/assets/_new/styles/uses.scss diff --git a/webapp/assets/_new/styles/export.scss b/webapp/assets/_new/styles/export.scss index e29c014e2..c8f16b750 100644 --- a/webapp/assets/_new/styles/export.scss +++ b/webapp/assets/_new/styles/export.scss @@ -20,6 +20,10 @@ backgroundColorPrimary: $background-color-primary; colorNeutral30: $color-neutral-30; + + chatSidemenuBg: $chat-sidemenu-bg; + chatSidemenuBackgroundOver: $chat-sidemenu-background-over; + chatSidemenuBackgroundActive: $chat-sidemenu-background-active; chatMessageColor: $chat-message-color; @@ -34,4 +38,8 @@ chatRoomBackgroundCounterBadge: $chat-room-background-counter-badge; chatRoomColorCounterBadge: $chat-room-color-counter-badge; + + chatIconAdd: $chat-icon-add; + chatIconSend: $chat-icon-send; + chatIconEmoji: $chat-icon-emoji; } \ No newline at end of file diff --git a/webapp/assets/_new/styles/tokens.scss b/webapp/assets/_new/styles/tokens.scss index dd3a042d1..578484355 100644 --- a/webapp/assets/_new/styles/tokens.scss +++ b/webapp/assets/_new/styles/tokens.scss @@ -403,10 +403,12 @@ $color-toast-green: $color-success; * @tokens Ribbon Color */ -$color-ribbon-event: $background-color-third; -$color-ribbon-event-active: $background-color-third-active; -$color-ribbon-article: $background-color-secondary; -$color-ribbon-article-active: $background-color-secondary-active; +$color-ribbon-announcement: $background-color-warning; +$color-ribbon-announcement-shadow: color.adjust($color-ribbon-announcement, $lightness: -20%,); +$color-ribbon-article: $background-color-secondary-active; +$color-ribbon-article-shadow: color.adjust($color-ribbon-article, $lightness: -20%); +$color-ribbon-event: $background-color-third-active; +$color-ribbon-event-shadow: color.adjust($color-ribbon-event, $lightness: -20%); /** * @tokens Chat Color @@ -415,10 +417,15 @@ $color-ribbon-article-active: $background-color-secondary-active; $chat-message-bg-me: $color-primary-light; $chat-message-color: $text-color-base; $chat-message-bg-others: $color-neutral-80; -$chat-sidemenu-bg: $color-secondary-active; +$chat-sidemenu-bg: white; +$chat-sidemenu-background-over: '#f6f6f6'; +$chat-sidemenu-background-active: $color-primary-light; $chat-new-message-color: $color-secondary-active; $chat-message-timestamp: $text-color-soft; $chat-message-checkmark-seen: $text-color-secondary; $chat-message-checkmark: $text-color-soft; $chat-room-color-counter-badge: $text-color-inverse; $chat-room-background-counter-badge: $color-secondary; +$chat-icon-add: $color-primary; +$chat-icon-send: $color-primary; +$chat-icon-emoji: $color-primary; diff --git a/webapp/assets/_new/styles/uses.scss b/webapp/assets/_new/styles/uses.scss new file mode 100644 index 000000000..37ad2e69c --- /dev/null +++ b/webapp/assets/_new/styles/uses.scss @@ -0,0 +1,4 @@ +// we can not use the command '@use' in 'tokens.scss' because of the compiler error 'SassError: @use rules must be written before any other rules.' +// therefore we integrate this file at first in 'nuxt.config.js' + +@use 'sass:color'; diff --git a/webapp/components/AvatarMenu/AvatarMenu.vue b/webapp/components/AvatarMenu/AvatarMenu.vue index ac583ed4b..d19f0bf95 100644 --- a/webapp/components/AvatarMenu/AvatarMenu.vue +++ b/webapp/components/AvatarMenu/AvatarMenu.vue @@ -168,10 +168,10 @@ export default { background-color: $color-neutral-90; } .logout-link { - color: $text-color-base; + color: $text-color-danger; padding-top: $space-xx-small; &:hover { - color: $text-color-link-active; + color: color.adjust($text-color-danger, $lightness: -10%); } } } diff --git a/webapp/components/LoginButton/LoginButton.vue b/webapp/components/LoginButton/LoginButton.vue index 39f0fa4ae..c2c154f34 100644 --- a/webapp/components/LoginButton/LoginButton.vue +++ b/webapp/components/LoginButton/LoginButton.vue @@ -40,7 +40,7 @@ export default { background-color: $color-neutral-90; } .login-link { - color: $text-color-base; + color: $text-color-link; padding-top: $space-xx-small; &:hover { color: $text-color-link-active; diff --git a/webapp/components/Ribbon/index.vue b/webapp/components/Ribbon/index.vue index 8595c0f5d..8011238fb 100644 --- a/webapp/components/Ribbon/index.vue +++ b/webapp/components/Ribbon/index.vue @@ -25,7 +25,7 @@ export default { padding: $size-ribbon $size-ribbon; border-radius: $border-radius-small 0 0 $border-radius-small; color: $color-neutral-100; - background-color: $color-ribbon-article-active; + background-color: $color-ribbon-article; font-size: $font-size-x-small; font-weight: $font-weight-bold; @@ -36,22 +36,23 @@ export default { bottom: -$size-ribbon; border-width: $border-size-large 4px $border-size-large $border-size-large; border-style: solid; - border-color: $color-ribbon-article transparent transparent $color-ribbon-article; + border-color: $color-ribbon-article-shadow transparent transparent $color-ribbon-article-shadow; } &.--pinned { - background-color: $color-warning; + background-color: $color-ribbon-announcement; &::before { - border-color: $color-warning transparent transparent $color-warning; + border-color: $color-ribbon-announcement-shadow transparent transparent + $color-ribbon-announcement-shadow; } } } .eventBg { - background-color: $color-ribbon-event-active; + background-color: $color-ribbon-event; &::before { - border-color: $color-ribbon-event transparent transparent $color-ribbon-event; + border-color: $color-ribbon-event-shadow transparent transparent $color-ribbon-event-shadow; } } diff --git a/webapp/components/_new/generic/BaseCard/BaseCard.vue b/webapp/components/_new/generic/BaseCard/BaseCard.vue index 86b1a6652..e6fcd5e49 100644 --- a/webapp/components/_new/generic/BaseCard/BaseCard.vue +++ b/webapp/components/_new/generic/BaseCard/BaseCard.vue @@ -64,7 +64,7 @@ export default { } &.--highlight { - border: $border-size-base solid $color-warning; + border: $border-size-base solid $color-ribbon-announcement; } &.--wide-content { diff --git a/webapp/constants/chat.js b/webapp/constants/chat.js index c278dfd62..d471b85b8 100644 --- a/webapp/constants/chat.js +++ b/webapp/constants/chat.js @@ -44,9 +44,9 @@ const STYLE = { }, sidemenu: { - background: '#fff', - backgroundHover: '#f6f6f6', - backgroundActive: styleData.colorPrimaryLight, + background: styleData.chatSidemenuBg, + backgroundHover: styleData.chatSidemenuBackgroundOver, + backgroundActive: styleData.chatSidemenuBackgroundActive, colorActive: '#1976d2', borderColorSearch: '#e1e5e8', }, @@ -114,12 +114,12 @@ const STYLE = { }, emoji: { - background: '#fff', + background: 'white', }, icons: { search: '#9ca6af', - add: styleData.colorPrimary, + add: styleData.chatIconAdd, toggle: styleData.colorNeutral30, menu: styleData.colorNeutral30, close: '#9ca6af', @@ -128,9 +128,9 @@ const STYLE = { paperclip: styleData.colorPrimary, closeOutline: '#000', closePreview: '#fff', - send: styleData.colorPrimary, + send: styleData.chatIconSend, sendDisabled: '#9ca6af', - emoji: styleData.colorPrimary, + emoji: styleData.chatIconEmoji, emojiReaction: 'rgba(0, 0, 0, 0.3)', document: styleData.colorPrimary, pencil: '#9e9e9e', diff --git a/webapp/nuxt.config.js b/webapp/nuxt.config.js index 9adacd4cc..b3bbdfc2d 100644 --- a/webapp/nuxt.config.js +++ b/webapp/nuxt.config.js @@ -102,6 +102,7 @@ export default { */ styleResources: { scss: [ + '~assets/_new/styles/uses.scss', styleguideStyles, '~assets/_new/styles/tokens.scss', '~assets/styles/imports/_branding.scss', From aee46552d679f4bbd2bfc81bcff11bc804207f18 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 17 Apr 2025 18:43:53 +0200 Subject: [PATCH 10/54] fix(backend): flaky notifications on mention in group unit test (#8404) --- ...tionsMiddleware.mentions-in-groups.spec.ts | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts index 575a2e86d..8bddff733 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts @@ -316,24 +316,32 @@ describe('mentions in groups', () => { }), ).resolves.toMatchObject({ data: { - notifications: [ + notifications: expect.arrayContaining([ { + createdAt: expect.any(String), from: { __typename: 'Post', id: 'public-post', + content: + 'Hey
@no-meber
@pending-member
@group-member.
! Please read this', }, read: false, reason: 'post_in_group', + relatedUser: null, }, { + createdAt: expect.any(String), from: { __typename: 'Post', id: 'public-post', + content: + 'Hey
@no-meber
@pending-member
@group-member.
! Please read this', }, read: false, reason: 'mentioned_in_post', + relatedUser: null, }, - ], + ]), }, errors: undefined, }) @@ -404,24 +412,32 @@ describe('mentions in groups', () => { }), ).resolves.toMatchObject({ data: { - notifications: [ + notifications: expect.arrayContaining([ { + createdAt: expect.any(String), from: { __typename: 'Post', id: 'closed-post', + content: + 'Hey members
@no-meber
@pending-member
@group-member.
! Please read this', }, read: false, reason: 'post_in_group', + relatedUser: null, }, { + createdAt: expect.any(String), from: { __typename: 'Post', id: 'closed-post', + content: + 'Hey members
@no-meber
@pending-member
@group-member.
! Please read this', }, read: false, reason: 'mentioned_in_post', + relatedUser: null, }, - ], + ]), }, errors: undefined, }) @@ -492,24 +508,32 @@ describe('mentions in groups', () => { }), ).resolves.toMatchObject({ data: { - notifications: [ + notifications: expect.arrayContaining([ { + createdAt: expect.any(String), from: { __typename: 'Post', id: 'hidden-post', + content: + 'Hey hiders
@no-meber
@pending-member
@group-member.
! Please read this', }, read: false, reason: 'post_in_group', + relatedUser: null, }, { + createdAt: expect.any(String), from: { __typename: 'Post', id: 'hidden-post', + content: + 'Hey hiders
@no-meber
@pending-member
@group-member.
! Please read this', }, read: false, reason: 'mentioned_in_post', + relatedUser: null, }, - ], + ]), }, errors: undefined, }) From de4325cb508199f1d9f1673c83c107fc10a1d0c2 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 17 Apr 2025 19:55:53 +0200 Subject: [PATCH 11/54] feat(backend): only one email is sent although more notifications are triggered (#8400) Co-authored-by: Ulf Gebhardt --- .../notificationsMiddleware.emails.spec.ts | 716 ++++++++++++++++++ ...tionsMiddleware.mentions-in-groups.spec.ts | 20 +- .../notifications/notificationsMiddleware.ts | 31 +- 3 files changed, 747 insertions(+), 20 deletions(-) create mode 100644 backend/src/middleware/notifications/notificationsMiddleware.emails.spec.ts diff --git a/backend/src/middleware/notifications/notificationsMiddleware.emails.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.emails.spec.ts new file mode 100644 index 000000000..497bf4c54 --- /dev/null +++ b/backend/src/middleware/notifications/notificationsMiddleware.emails.spec.ts @@ -0,0 +1,716 @@ +import { createTestClient } from 'apollo-server-testing' +import gql from 'graphql-tag' + +import Factory, { cleanDatabase } from '@db/factories' +import { getNeode, getDriver } from '@db/neo4j' +import { createGroupMutation, joinGroupMutation } from '@graphql/groups' +import CONFIG from '@src/config' +import createServer from '@src/server' + +CONFIG.CATEGORIES_ACTIVE = false + +const sendMailMock = jest.fn() +jest.mock('../helpers/email/sendMail', () => ({ + sendMail: () => sendMailMock(), +})) + +let server, query, mutate, authenticatedUser + +let postAuthor, groupMember + +const driver = getDriver() +const neode = getNeode() + +const mentionString = + '@group-member' + +const createPostMutation = gql` + mutation ($id: ID, $title: String!, $content: String!, $groupId: ID) { + CreatePost(id: $id, title: $title, content: $content, groupId: $groupId) { + id + title + content + } + } +` + +const createCommentMutation = gql` + mutation ($id: ID, $postId: ID!, $content: String!) { + CreateComment(id: $id, postId: $postId, content: $content) { + id + content + } + } +` + +const notificationQuery = gql` + query ($read: Boolean) { + notifications(read: $read, orderBy: updatedAt_desc) { + read + reason + createdAt + relatedUser { + id + } + from { + __typename + ... on Post { + id + content + } + ... on Comment { + id + content + } + ... on Group { + id + } + } + } + } +` + +const followUserMutation = gql` + mutation ($id: ID!) { + followUser(id: $id) { + id + } + } +` + +const markAllAsRead = async () => + mutate({ + mutation: gql` + mutation { + markAllAsRead { + id + } + } + `, + }) + +beforeAll(async () => { + await cleanDatabase() + + const createServerResult = createServer({ + context: () => { + return { + user: authenticatedUser, + neode, + driver, + cypherParams: { + currentUserId: authenticatedUser ? authenticatedUser.id : null, + }, + } + }, + }) + server = createServerResult.server + const createTestClientResult = createTestClient(server) + query = createTestClientResult.query + mutate = createTestClientResult.mutate +}) + +afterAll(async () => { + await cleanDatabase() + driver.close() +}) + +describe('emails sent for notifications', () => { + beforeEach(async () => { + postAuthor = await Factory.build( + 'user', + { + id: 'post-author', + name: 'Post Author', + slug: 'post-author', + }, + { + email: 'test@example.org', + password: '1234', + }, + ) + groupMember = await Factory.build( + 'user', + { + id: 'group-member', + name: 'Group Member', + slug: 'group-member', + }, + { + email: 'group.member@example.org', + password: '1234', + }, + ) + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createGroupMutation(), + variables: { + id: 'public-group', + name: 'A public group', + description: 'A public group to test the notifications of mentions', + groupType: 'public', + actionRadius: 'national', + }, + }) + authenticatedUser = await groupMember.toJson() + await mutate({ + mutation: joinGroupMutation(), + variables: { + groupId: 'public-group', + userId: 'group-member', + }, + }) + await mutate({ + mutation: followUserMutation, + variables: { id: 'post-author' }, + }) + }) + + afterEach(async () => { + await cleanDatabase() + }) + + describe('handleContentDataOfPost', () => { + describe('post-author posts into group and mentions following group-member', () => { + describe('all email notification settings are true', () => { + beforeEach(async () => { + jest.clearAllMocks() + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createPostMutation, + variables: { + id: 'post', + title: 'This is the post', + content: `Hello, ${mentionString}, my trusty follower.`, + groupId: 'public-group', + }, + }) + }) + + it('sends only one email', () => { + expect(sendMailMock).toHaveBeenCalledTimes(1) + }) + + it('sends 3 notifications', async () => { + authenticatedUser = await groupMember.toJson() + await expect( + query({ + query: notificationQuery, + }), + ).resolves.toMatchObject({ + data: { + notifications: expect.arrayContaining([ + { + createdAt: expect.any(String), + from: { + __typename: 'Post', + id: 'post', + content: + 'Hello, @group-member, my trusty follower.', + }, + read: false, + reason: 'post_in_group', + relatedUser: null, + }, + { + createdAt: expect.any(String), + from: { + __typename: 'Post', + id: 'post', + content: + 'Hello, @group-member, my trusty follower.', + }, + read: false, + reason: 'followed_user_posted', + relatedUser: null, + }, + { + createdAt: expect.any(String), + from: { + __typename: 'Post', + id: 'post', + content: + 'Hello, @group-member, my trusty follower.', + }, + read: false, + reason: 'mentioned_in_post', + relatedUser: null, + }, + ]), + }, + errors: undefined, + }) + }) + }) + + describe('email notification for mention in post is false', () => { + beforeEach(async () => { + jest.clearAllMocks() + await groupMember.update({ emailNotificationsMention: false }) + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createPostMutation, + variables: { + id: 'post', + title: 'This is the post', + content: `Hello, ${mentionString}, my trusty follower.`, + groupId: 'public-group', + }, + }) + }) + + it('sends only one email', () => { + expect(sendMailMock).toHaveBeenCalledTimes(1) + }) + + it('sends 3 notifications', async () => { + authenticatedUser = await groupMember.toJson() + await expect( + query({ + query: notificationQuery, + }), + ).resolves.toMatchObject({ + data: { + notifications: expect.arrayContaining([ + { + createdAt: expect.any(String), + from: { + __typename: 'Post', + id: 'post', + content: + 'Hello, @group-member, my trusty follower.', + }, + read: false, + reason: 'post_in_group', + relatedUser: null, + }, + { + createdAt: expect.any(String), + from: { + __typename: 'Post', + id: 'post', + content: + 'Hello, @group-member, my trusty follower.', + }, + read: false, + reason: 'followed_user_posted', + relatedUser: null, + }, + { + createdAt: expect.any(String), + from: { + __typename: 'Post', + id: 'post', + content: + 'Hello, @group-member, my trusty follower.', + }, + read: false, + reason: 'mentioned_in_post', + relatedUser: null, + }, + ]), + }, + errors: undefined, + }) + }) + }) + + describe('email notification for mention in post and followed users is false', () => { + beforeEach(async () => { + jest.clearAllMocks() + await groupMember.update({ emailNotificationsMention: false }) + await groupMember.update({ emailNotificationsFollowingUsers: false }) + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createPostMutation, + variables: { + id: 'post', + title: 'This is the post', + content: `Hello, ${mentionString}, my trusty follower.`, + groupId: 'public-group', + }, + }) + }) + + it('sends only one email', () => { + expect(sendMailMock).toHaveBeenCalledTimes(1) + }) + + it('sends 3 notifications', async () => { + authenticatedUser = await groupMember.toJson() + await expect( + query({ + query: notificationQuery, + }), + ).resolves.toMatchObject({ + data: { + notifications: expect.arrayContaining([ + { + createdAt: expect.any(String), + from: { + __typename: 'Post', + id: 'post', + content: + 'Hello, @group-member, my trusty follower.', + }, + read: false, + reason: 'post_in_group', + relatedUser: null, + }, + { + createdAt: expect.any(String), + from: { + __typename: 'Post', + id: 'post', + content: + 'Hello, @group-member, my trusty follower.', + }, + read: false, + reason: 'followed_user_posted', + relatedUser: null, + }, + { + createdAt: expect.any(String), + from: { + __typename: 'Post', + id: 'post', + content: + 'Hello, @group-member, my trusty follower.', + }, + read: false, + reason: 'mentioned_in_post', + relatedUser: null, + }, + ]), + }, + errors: undefined, + }) + }) + }) + + describe('all relevant email notifications are false', () => { + beforeEach(async () => { + jest.clearAllMocks() + await groupMember.update({ emailNotificationsMention: false }) + await groupMember.update({ emailNotificationsFollowingUsers: false }) + await groupMember.update({ emailNotificationsPostInGroup: false }) + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createPostMutation, + variables: { + id: 'post', + title: 'This is the post', + content: `Hello, ${mentionString}, my trusty follower.`, + groupId: 'public-group', + }, + }) + }) + + it('sends NO email', () => { + expect(sendMailMock).not.toHaveBeenCalled() + }) + + it('sends 3 notifications', async () => { + authenticatedUser = await groupMember.toJson() + await expect( + query({ + query: notificationQuery, + }), + ).resolves.toMatchObject({ + data: { + notifications: expect.arrayContaining([ + { + createdAt: expect.any(String), + from: { + __typename: 'Post', + id: 'post', + content: + 'Hello, @group-member, my trusty follower.', + }, + read: false, + reason: 'post_in_group', + relatedUser: null, + }, + { + createdAt: expect.any(String), + from: { + __typename: 'Post', + id: 'post', + content: + 'Hello, @group-member, my trusty follower.', + }, + read: false, + reason: 'followed_user_posted', + relatedUser: null, + }, + { + createdAt: expect.any(String), + from: { + __typename: 'Post', + id: 'post', + content: + 'Hello, @group-member, my trusty follower.', + }, + read: false, + reason: 'mentioned_in_post', + relatedUser: null, + }, + ]), + }, + errors: undefined, + }) + }) + }) + }) + }) + + describe('handleContentDataOfComment', () => { + describe('user comments post and author responds with in a comment and mentions the user', () => { + describe('all email notification settings are true', () => { + beforeEach(async () => { + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createPostMutation, + variables: { + id: 'post', + title: 'This is the post', + content: `Hello, ${mentionString}, my trusty follower.`, + groupId: 'public-group', + }, + }) + authenticatedUser = await groupMember.toJson() + await mutate({ + mutation: createCommentMutation, + variables: { + id: 'comment', + content: `Hello, my beloved author.`, + postId: 'post', + }, + }) + await markAllAsRead() + jest.clearAllMocks() + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createCommentMutation, + variables: { + id: 'comment-2', + content: `Hello, ${mentionString}, my beloved follower.`, + postId: 'post', + }, + }) + }) + + it('sends only one email', () => { + expect(sendMailMock).toHaveBeenCalledTimes(1) + }) + + it('sends 2 notifications', async () => { + authenticatedUser = await groupMember.toJson() + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toMatchObject({ + data: { + notifications: expect.arrayContaining([ + { + createdAt: expect.any(String), + from: { + __typename: 'Comment', + id: 'comment-2', + content: + 'Hello, @group-member, my beloved follower.', + }, + read: false, + reason: 'commented_on_post', + relatedUser: null, + }, + { + createdAt: expect.any(String), + from: { + __typename: 'Comment', + id: 'comment-2', + content: + 'Hello, @group-member, my beloved follower.', + }, + read: false, + reason: 'mentioned_in_comment', + relatedUser: null, + }, + ]), + }, + errors: undefined, + }) + }) + }) + + describe('email notification commented on post is false', () => { + beforeEach(async () => { + await groupMember.update({ emailNotificationsCommentOnObservedPost: false }) + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createPostMutation, + variables: { + id: 'post', + title: 'This is the post', + content: `Hello, ${mentionString}, my trusty follower.`, + groupId: 'public-group', + }, + }) + authenticatedUser = await groupMember.toJson() + await mutate({ + mutation: createCommentMutation, + variables: { + id: 'comment', + content: `Hello, my beloved author.`, + postId: 'post', + }, + }) + await markAllAsRead() + jest.clearAllMocks() + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createCommentMutation, + variables: { + id: 'comment-2', + content: `Hello, ${mentionString}, my beloved follower.`, + postId: 'post', + }, + }) + }) + + it('sends only one email', () => { + expect(sendMailMock).toHaveBeenCalledTimes(1) + }) + + it('sends 2 notifications', async () => { + authenticatedUser = await groupMember.toJson() + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toMatchObject({ + data: { + notifications: expect.arrayContaining([ + { + createdAt: expect.any(String), + from: { + __typename: 'Comment', + id: 'comment-2', + content: + 'Hello, @group-member, my beloved follower.', + }, + read: false, + reason: 'commented_on_post', + relatedUser: null, + }, + { + createdAt: expect.any(String), + from: { + __typename: 'Comment', + id: 'comment-2', + content: + 'Hello, @group-member, my beloved follower.', + }, + read: false, + reason: 'mentioned_in_comment', + relatedUser: null, + }, + ]), + }, + errors: undefined, + }) + }) + }) + + describe('all relevant email notifications are false', () => { + beforeEach(async () => { + await groupMember.update({ emailNotificationsCommentOnObservedPost: false }) + await groupMember.update({ emailNotificationsMention: false }) + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createPostMutation, + variables: { + id: 'post', + title: 'This is the post', + content: `Hello, ${mentionString}, my trusty follower.`, + groupId: 'public-group', + }, + }) + authenticatedUser = await groupMember.toJson() + await mutate({ + mutation: createCommentMutation, + variables: { + id: 'comment', + content: `Hello, my beloved author.`, + postId: 'post', + }, + }) + await markAllAsRead() + jest.clearAllMocks() + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createCommentMutation, + variables: { + id: 'comment-2', + content: `Hello, ${mentionString}, my beloved follower.`, + postId: 'post', + }, + }) + }) + + it('sends NO email', () => { + expect(sendMailMock).not.toHaveBeenCalled() + }) + + it('sends 2 notifications', async () => { + authenticatedUser = await groupMember.toJson() + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toMatchObject({ + data: { + notifications: expect.arrayContaining([ + { + createdAt: expect.any(String), + from: { + __typename: 'Comment', + id: 'comment-2', + content: + 'Hello, @group-member, my beloved follower.', + }, + read: false, + reason: 'commented_on_post', + relatedUser: null, + }, + { + createdAt: expect.any(String), + from: { + __typename: 'Comment', + id: 'comment-2', + content: + 'Hello, @group-member, my beloved follower.', + }, + read: false, + reason: 'mentioned_in_comment', + relatedUser: null, + }, + ]), + }, + errors: undefined, + }) + }) + }) + }) + }) +}) diff --git a/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts index 8bddff733..49763b4c3 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts @@ -124,7 +124,7 @@ describe('mentions in groups', () => { slug: 'post-author', }, { - email: 'test@example.org', + email: 'post.author@example.org', password: '1234', }, ) @@ -136,7 +136,7 @@ describe('mentions in groups', () => { slug: 'group-member', }, { - email: 'test2@example.org', + email: 'group.member@example.org', password: '1234', }, ) @@ -148,7 +148,7 @@ describe('mentions in groups', () => { slug: 'pending-member', }, { - email: 'test3@example.org', + email: 'pending.member@example.org', password: '1234', }, ) @@ -160,7 +160,7 @@ describe('mentions in groups', () => { slug: 'no-member', }, { - email: 'test4@example.org', + email: 'no.member@example.org', password: '1234', }, ) @@ -347,8 +347,8 @@ describe('mentions in groups', () => { }) }) - it('sends 3 emails, 2 mentions and 1 post in group', () => { - expect(sendMailMock).toHaveBeenCalledTimes(5) + it('sends 3 emails, one for each user', () => { + expect(sendMailMock).toHaveBeenCalledTimes(3) }) }) @@ -443,8 +443,8 @@ describe('mentions in groups', () => { }) }) - it('sends 2 emails, one mention and one post in group', () => { - expect(sendMailMock).toHaveBeenCalledTimes(2) + it('sends only 1 email', () => { + expect(sendMailMock).toHaveBeenCalledTimes(1) }) }) @@ -539,8 +539,8 @@ describe('mentions in groups', () => { }) }) - it('sends 2 emails, one mention and one post in group', () => { - expect(sendMailMock).toHaveBeenCalledTimes(2) + it('sends only 1 email', () => { + expect(sendMailMock).toHaveBeenCalledTimes(1) }) }) diff --git a/backend/src/middleware/notifications/notificationsMiddleware.ts b/backend/src/middleware/notifications/notificationsMiddleware.ts index 27216988f..4fb8cba93 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.ts @@ -40,7 +40,12 @@ const queryNotificationEmails = async (context, notificationUserIds) => { } } -const publishNotifications = async (context, promises, emailNotificationSetting: string) => { +const publishNotifications = async ( + context, + promises, + emailNotificationSetting: string, + emailsSent: string[] = [], +): Promise => { let notifications = await Promise.all(promises) notifications = notifications.flat() const notificationsEmailAddresses = await queryNotificationEmails( @@ -51,7 +56,8 @@ const publishNotifications = async (context, promises, emailNotificationSetting: pubsub.publish(NOTIFICATION_ADDED, { notificationAdded }) if ( (notificationAdded.to[emailNotificationSetting] ?? true) && - !isUserOnline(notificationAdded.to) + !isUserOnline(notificationAdded.to) && + !emailsSent.includes(notificationsEmailAddresses[index].email) ) { sendMail( notificationTemplate({ @@ -59,8 +65,10 @@ const publishNotifications = async (context, promises, emailNotificationSetting: variables: { notification: notificationAdded }, }), ) + emailsSent.push(notificationsEmailAddresses[index].email) } }) + return emailsSent } const handleJoinGroup = async (resolve, root, args, context, resolveInfo) => { @@ -120,20 +128,24 @@ const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo const idsOfUsers = extractMentionedUsers(args.content) const post = await resolve(root, args, context, resolveInfo) if (post) { - await publishNotifications( + const sentEmails: string[] = await publishNotifications( context, [notifyUsersOfMention('Post', post.id, idsOfUsers, 'mentioned_in_post', context)], 'emailNotificationsMention', ) - await publishNotifications( - context, - [notifyFollowingUsers(post.id, groupId, context)], - 'emailNotificationsFollowingUsers', + sentEmails.concat( + await publishNotifications( + context, + [notifyFollowingUsers(post.id, groupId, context)], + 'emailNotificationsFollowingUsers', + sentEmails, + ), ) await publishNotifications( context, [notifyGroupMembersOfNewPost(post.id, groupId, context)], 'emailNotificationsPostInGroup', + sentEmails, ) } return post @@ -145,7 +157,7 @@ const handleContentDataOfComment = async (resolve, root, args, context, resolveI const comment = await resolve(root, args, context, resolveInfo) const [postAuthor] = await postAuthorOfComment(comment.id, { context }) idsOfMentionedUsers = idsOfMentionedUsers.filter((id) => id !== postAuthor.id) - await publishNotifications( + const sentEmails: string[] = await publishNotifications( context, [ notifyUsersOfMention( @@ -158,13 +170,12 @@ const handleContentDataOfComment = async (resolve, root, args, context, resolveI ], 'emailNotificationsMention', ) - await publishNotifications( context, [notifyUsersOfComment('Comment', comment.id, 'commented_on_post', context)], 'emailNotificationsCommentOnObservedPost', + sentEmails, ) - return comment } From 89b0fa7a51a56f8d53802d23cb47e17c80c3a808 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 17 Apr 2025 20:22:20 +0200 Subject: [PATCH 12/54] feat(backend): do not notify blocked or muted users (#8403) * no more notifications of blocked or muted users in groups * do not receive notifications on mentions or observed posts from muted users --- ...icationsMiddleware.posts-in-groups.spec.ts | 144 +++++++++++++----- .../notificationsMiddleware.spec.ts | 127 +++++++++++++++ .../notifications/notificationsMiddleware.ts | 13 +- 3 files changed, 240 insertions(+), 44 deletions(-) diff --git a/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts index a46de2830..ad336596d 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts @@ -118,7 +118,7 @@ afterAll(async () => { }) describe('notify group members of new posts in group', () => { - beforeAll(async () => { + beforeEach(async () => { postAuthor = await Factory.build( 'user', { @@ -193,8 +193,12 @@ describe('notify group members of new posts in group', () => { }) }) + afterEach(async () => { + await cleanDatabase() + }) + describe('group owner posts in group', () => { - beforeAll(async () => { + beforeEach(async () => { jest.clearAllMocks() authenticatedUser = await groupMember.toJson() await markAllAsRead() @@ -275,29 +279,15 @@ describe('notify group members of new posts in group', () => { }) describe('group member mutes group', () => { - it('sets the muted status correctly', async () => { + beforeEach(async () => { authenticatedUser = await groupMember.toJson() - await expect( - mutate({ - mutation: muteGroupMutation, - variables: { - groupId: 'g-1', - }, - }), - ).resolves.toMatchObject({ - data: { - muteGroup: { - isMutedByMe: true, - }, + await mutate({ + mutation: muteGroupMutation, + variables: { + groupId: 'g-1', }, - errors: undefined, }) - }) - - it('sends NO notification when another post is posted', async () => { jest.clearAllMocks() - authenticatedUser = await groupMember.toJson() - await markAllAsRead() authenticatedUser = await postAuthor.toJson() await mutate({ mutation: createPostMutation, @@ -308,7 +298,9 @@ describe('notify group members of new posts in group', () => { groupId: 'g-1', }, }) - authenticatedUser = await groupMember.toJson() + }) + + it('sends NO notification when another post is posted', async () => { await expect( query({ query: notificationQuery, @@ -329,30 +321,18 @@ describe('notify group members of new posts in group', () => { }) describe('group member unmutes group again but disables email', () => { - beforeAll(async () => { + beforeEach(async () => { + authenticatedUser = await groupMember.toJson() + await mutate({ + mutation: unmuteGroupMutation, + variables: { + groupId: 'g-1', + }, + }) jest.clearAllMocks() await groupMember.update({ emailNotificationsPostInGroup: false }) }) - it('sets the muted status correctly', async () => { - authenticatedUser = await groupMember.toJson() - await expect( - mutate({ - mutation: unmuteGroupMutation, - variables: { - groupId: 'g-1', - }, - }), - ).resolves.toMatchObject({ - data: { - unmuteGroup: { - isMutedByMe: false, - }, - }, - errors: undefined, - }) - }) - it('sends notification when another post is posted', async () => { authenticatedUser = await groupMember.toJson() await markAllAsRead() @@ -396,5 +376,85 @@ describe('notify group members of new posts in group', () => { }) }) }) + + describe('group member blocks author', () => { + beforeEach(async () => { + await groupMember.relateTo(postAuthor, 'blocked') + authenticatedUser = await groupMember.toJson() + await markAllAsRead() + jest.clearAllMocks() + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createPostMutation, + variables: { + id: 'post-1', + title: 'This is another post in the group', + content: 'This is the content of another post in the group', + groupId: 'g-1', + }, + }) + }) + + it('sends no notification to the user', async () => { + authenticatedUser = await groupMember.toJson() + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toMatchObject({ + data: { + notifications: [], + }, + errors: undefined, + }) + }) + + it('sends NO email', () => { + expect(sendMailMock).not.toHaveBeenCalled() + }) + }) + + describe('group member mutes author', () => { + beforeEach(async () => { + await groupMember.relateTo(postAuthor, 'muted') + authenticatedUser = await groupMember.toJson() + await markAllAsRead() + jest.clearAllMocks() + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createPostMutation, + variables: { + id: 'post-1', + title: 'This is another post in the group', + content: 'This is the content of another post in the group', + groupId: 'g-1', + }, + }) + }) + + it('sends no notification to the user', async () => { + authenticatedUser = await groupMember.toJson() + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toMatchObject({ + data: { + notifications: [], + }, + errors: undefined, + }) + }) + + it('sends NO email', () => { + expect(sendMailMock).not.toHaveBeenCalled() + }) + }) }) }) diff --git a/backend/src/middleware/notifications/notificationsMiddleware.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.spec.ts index 90888cf8b..c58acc5e2 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.spec.ts @@ -294,6 +294,25 @@ describe('notifications', () => { ).resolves.toEqual(expected) }) }) + + describe('if I have muted the comment author', () => { + it('sends me no notification', async () => { + await notifiedUser.relateTo(commentAuthor, 'muted') + await createCommentOnPostAction() + const expected = expect.objectContaining({ + data: { notifications: [] }, + }) + + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toEqual(expected) + }) + }) }) describe('commenter is me', () => { @@ -581,6 +600,48 @@ describe('notifications', () => { expect(pubsubSpy).not.toHaveBeenCalled() }) }) + + describe('but the author of the post muted me', () => { + beforeEach(async () => { + await postAuthor.relateTo(notifiedUser, 'muted') + }) + + it('sends me a notification', async () => { + await createPostAction() + const expected = expect.objectContaining({ + data: { + notifications: [ + { + createdAt: expect.any(String), + from: { + __typename: 'Post', + content: + 'Hey @al-capone how do you do?', + id: 'p47', + }, + read: false, + reason: 'mentioned_in_post', + relatedUser: null, + }, + ], + }, + }) + + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toEqual(expected) + }) + + it('publishes `NOTIFICATION_ADDED`', async () => { + await createPostAction() + expect(pubsubSpy).toHaveBeenCalled() + }) + }) }) describe('mentions me in a comment', () => { @@ -736,6 +797,72 @@ describe('notifications', () => { expect(pubsubSpy).toHaveBeenCalledTimes(1) }) }) + + describe('but the author of the post muted me', () => { + beforeEach(async () => { + await postAuthor.relateTo(notifiedUser, 'muted') + commentContent = + 'One mention about me with @al-capone.' + commentAuthor = await neode.create( + 'User', + { + id: 'commentAuthor', + name: 'Mrs Comment', + slug: 'mrs-comment', + }, + { + email: 'comment-author@example.org', + password: '1234', + }, + ) + }) + + it('sends me a notification', async () => { + await createCommentOnPostAction() + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toMatchObject({ + data: { + notifications: [ + { + createdAt: expect.any(String), + from: { + __typename: 'Comment', + content: + 'One mention about me with @al-capone.', + id: 'c47', + }, + read: false, + reason: 'mentioned_in_comment', + relatedUser: null, + }, + ], + }, + errors: undefined, + }) + }) + + it('publishes `NOTIFICATION_ADDED` to authenticated user and me', async () => { + await createCommentOnPostAction() + expect(pubsubSpy).toHaveBeenCalledWith( + 'NOTIFICATION_ADDED', + expect.objectContaining({ + notificationAdded: expect.objectContaining({ + reason: 'commented_on_post', + to: expect.objectContaining({ + id: 'postAuthor', // that's expected, it's not me but the post author + }), + }), + }), + ) + expect(pubsubSpy).toHaveBeenCalledTimes(2) + }) + }) }) }) }) diff --git a/backend/src/middleware/notifications/notificationsMiddleware.ts b/backend/src/middleware/notifications/notificationsMiddleware.ts index 4fb8cba93..a8d95b284 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.ts @@ -245,6 +245,8 @@ const notifyGroupMembersOfNewPost = async (postId, groupId, context) => { MATCH (post)-[:IN]->(group:Group { id: $groupId })<-[membership:MEMBER_OF]-(user:User) WHERE NOT membership.role = 'pending' AND NOT (user)-[:MUTED]->(group) + AND NOT (user)-[:MUTED]->(author) + AND NOT (user)-[:BLOCKED]-(author) AND NOT user.id = $userId WITH post, author, user MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user) @@ -360,7 +362,10 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => { case 'mentioned_in_post': { mentionedCypher = ` MATCH (post: Post { id: $id })<-[:WROTE]-(author: User) - MATCH (user: User) WHERE user.id in $idsOfUsers AND NOT (user)-[:BLOCKED]-(author) + MATCH (user: User) + WHERE user.id in $idsOfUsers + AND NOT (user)-[:BLOCKED]-(author) + AND NOT (user)-[:MUTED]->(author) OPTIONAL MATCH (post)-[:IN]->(group:Group) OPTIONAL MATCH (group)<-[membership:MEMBER_OF]-(user) WITH post, author, user, group WHERE group IS NULL OR group.groupType = 'public' OR membership.role IN ['usual', 'admin', 'owner'] @@ -376,6 +381,8 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => { WHERE user.id in $idsOfUsers AND NOT (user)-[:BLOCKED]-(commenter) AND NOT (user)-[:BLOCKED]-(postAuthor) + AND NOT (user)-[:MUTED]->(commenter) + AND NOT (user)-[:MUTED]->(postAuthor) OPTIONAL MATCH (post)-[:IN]->(group:Group) OPTIONAL MATCH (group)<-[membership:MEMBER_OF]-(user) WITH comment, user, group WHERE group IS NULL OR group.groupType = 'public' OR membership.role IN ['usual', 'admin', 'owner'] @@ -422,7 +429,9 @@ const notifyUsersOfComment = async (label, commentId, reason, context) => { const notificationTransactionResponse = await transaction.run( ` MATCH (observingUser:User)-[:OBSERVES { active: true }]->(post:Post)<-[:COMMENTS]-(comment:Comment { id: $commentId })<-[:WROTE]-(commenter:User) - WHERE NOT (observingUser)-[:BLOCKED]-(commenter) AND NOT observingUser.id = $userId + WHERE NOT (observingUser)-[:BLOCKED]-(commenter) + AND NOT (observingUser)-[:MUTED]->(commenter) + AND NOT observingUser.id = $userId WITH observingUser, post, comment, commenter MATCH (postAuthor:User)-[:WROTE]->(post) MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(observingUser) From 8cf405c5494b22afcd698cdde058f69c98efccd7 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 18 Apr 2025 01:08:54 +0200 Subject: [PATCH 13/54] feat(backend): badges (#8391) * delete all old badges * reward/unrewardBadge * verification Badges * name all badged accordingly * more tests, lint * seed badges * profileBadge mechanic * badgesUnusedCount * seed profileBadges set * configure profile badge count * insert badges db:data:badges:default * seed commands to seed default badges and allow to seed branding data * copy data migrations when building docker * typo * correct data:branding command & document it * test new functionality * Update backend/src/db/seed/badges.ts Co-authored-by: Max * Update backend/src/db/seed/badges.ts Co-authored-by: Max * Update backend/src/db/seed/badges.ts Co-authored-by: Max * naming coventions * final naming fix lint fix build fix badge type in test renamed badge_ to trophy_ lint fixes small renameing fixes fix users spec fix webapp queries fix display * expose badge description --------- Co-authored-by: Max --- backend/Dockerfile | 1 + backend/README.md | 14 + backend/branding/data/.gitkeep | 0 backend/package.json | 2 + ...egogo_en_bear.svg => trophy_blue_bear.svg} | 0 ...ogo_en_panda.svg => trophy_blue_panda.svg} | 0 ...o_en_rabbit.svg => trophy_blue_rabbit.svg} | 0 ...o_en_racoon.svg => trophy_blue_racoon.svg} | 0 ...ogo_en_rhino.svg => trophy_blue_rhino.svg} | 0 ...ogo_en_tiger.svg => trophy_blue_tiger.svg} | 0 ...o_en_turtle.svg => trophy_blue_turtle.svg} | 0 ...ogo_en_whale.svg => trophy_blue_whale.svg} | 0 ...egogo_en_wolf.svg => trophy_blue_wolf.svg} | 0 ...e_airship.svg => trophy_green_airship.svg} | 0 ...ienship.svg => trophy_green_alienship.svg} | 0 ...e_balloon.svg => trophy_green_balloon.svg} | 0 ...wooold_de_bee.svg => trophy_green_bee.svg} | 0 ...alloon.svg => trophy_green_bigballoon.svg} | 0 ...tterfly.svg => trophy_green_butterfly.svg} | 0 ...ox_de_crane.svg => trophy_green_crane.svg} | 0 ...bow.svg => trophy_green_doublerainbow.svg} | 0 ...ainbow.svg => trophy_green_endrainbow.svg} | 0 ..._de_flower.svg => trophy_green_flower.svg} | 0 ..._de_glider.svg => trophy_green_glider.svg} | 0 ...copter.svg => trophy_green_helicopter.svg} | 0 ...lifetree.svg => trophy_green_lifetree.svg} | 0 ...nbow.svg => trophy_green_magicrainbow.svg} | 0 ...e_starter.svg => trophy_green_starter.svg} | 0 ...nder.svg => trophy_green_superfounder.svg} | 0 ...e_admin.svg => verification_red_admin.svg} | 0 ...per.svg => verification_red_developer.svg} | 0 ...tor.svg => verification_red_moderator.svg} | 0 backend/src/constants/badges.ts | 2 + backend/src/db/badges.ts | 13 + backend/src/db/data-branding.ts | 17 + backend/src/db/data/.gitkeep | 0 .../20250414220436-delete-old-badges.ts | 49 ++ backend/src/db/seed.ts | 97 ++- backend/src/db/seed/badges.ts | 185 +++++ .../src/middleware/permissionsMiddleware.ts | 9 +- backend/src/models/Badge.ts | 4 +- backend/src/models/User.ts | 18 + backend/src/schema/resolvers/badges.spec.ts | 667 ++++++++++++++++++ backend/src/schema/resolvers/badges.ts | 115 ++- backend/src/schema/resolvers/rewards.spec.ts | 352 --------- backend/src/schema/resolvers/rewards.ts | 47 -- backend/src/schema/resolvers/users.spec.ts | 306 ++++++++ backend/src/schema/resolvers/users.ts | 154 +++- backend/src/schema/types/type/Badge.gql | 18 +- backend/src/schema/types/type/User.gql | 11 +- .../CommentCard/CommentCard.story.js | 4 +- .../components/PostTeaser/PostTeaser.story.js | 4 +- .../components/UserTeaser/UserTeaser.story.js | 4 +- webapp/constants/badges.js | 2 + webapp/graphql/CommentMutations.js | 2 +- webapp/graphql/Fragments.js | 2 +- webapp/pages/profile/_id/_slug.vue | 4 +- 57 files changed, 1639 insertions(+), 464 deletions(-) create mode 100644 backend/branding/data/.gitkeep rename backend/public/img/badges/{indiegogo_en_bear.svg => trophy_blue_bear.svg} (100%) rename backend/public/img/badges/{indiegogo_en_panda.svg => trophy_blue_panda.svg} (100%) rename backend/public/img/badges/{indiegogo_en_rabbit.svg => trophy_blue_rabbit.svg} (100%) rename backend/public/img/badges/{indiegogo_en_racoon.svg => trophy_blue_racoon.svg} (100%) rename backend/public/img/badges/{indiegogo_en_rhino.svg => trophy_blue_rhino.svg} (100%) rename backend/public/img/badges/{indiegogo_en_tiger.svg => trophy_blue_tiger.svg} (100%) rename backend/public/img/badges/{indiegogo_en_turtle.svg => trophy_blue_turtle.svg} (100%) rename backend/public/img/badges/{indiegogo_en_whale.svg => trophy_blue_whale.svg} (100%) rename backend/public/img/badges/{indiegogo_en_wolf.svg => trophy_blue_wolf.svg} (100%) rename backend/public/img/badges/{fundraisingbox_de_airship.svg => trophy_green_airship.svg} (100%) rename backend/public/img/badges/{fundraisingbox_de_alienship.svg => trophy_green_alienship.svg} (100%) rename backend/public/img/badges/{fundraisingbox_de_balloon.svg => trophy_green_balloon.svg} (100%) rename backend/public/img/badges/{wooold_de_bee.svg => trophy_green_bee.svg} (100%) rename backend/public/img/badges/{fundraisingbox_de_bigballoon.svg => trophy_green_bigballoon.svg} (100%) rename backend/public/img/badges/{wooold_de_butterfly.svg => trophy_green_butterfly.svg} (100%) rename backend/public/img/badges/{fundraisingbox_de_crane.svg => trophy_green_crane.svg} (100%) rename backend/public/img/badges/{wooold_de_double_rainbow.svg => trophy_green_doublerainbow.svg} (100%) rename backend/public/img/badges/{wooold_de_end_of_rainbow.svg => trophy_green_endrainbow.svg} (100%) rename backend/public/img/badges/{wooold_de_flower.svg => trophy_green_flower.svg} (100%) rename backend/public/img/badges/{fundraisingbox_de_glider.svg => trophy_green_glider.svg} (100%) rename backend/public/img/badges/{fundraisingbox_de_helicopter.svg => trophy_green_helicopter.svg} (100%) rename backend/public/img/badges/{wooold_de_lifetree.svg => trophy_green_lifetree.svg} (100%) rename backend/public/img/badges/{wooold_de_magic_rainbow.svg => trophy_green_magicrainbow.svg} (100%) rename backend/public/img/badges/{fundraisingbox_de_starter.svg => trophy_green_starter.svg} (100%) rename backend/public/img/badges/{wooold_de_super_founder.svg => trophy_green_superfounder.svg} (100%) rename backend/public/img/badges/{user_role_admin.svg => verification_red_admin.svg} (100%) rename backend/public/img/badges/{user_role_developer.svg => verification_red_developer.svg} (100%) rename backend/public/img/badges/{user_role_moderator.svg => verification_red_moderator.svg} (100%) create mode 100644 backend/src/constants/badges.ts create mode 100644 backend/src/db/badges.ts create mode 100644 backend/src/db/data-branding.ts create mode 100644 backend/src/db/data/.gitkeep create mode 100644 backend/src/db/migrations/20250414220436-delete-old-badges.ts create mode 100644 backend/src/db/seed/badges.ts create mode 100644 backend/src/schema/resolvers/badges.spec.ts delete mode 100644 backend/src/schema/resolvers/rewards.spec.ts delete mode 100644 backend/src/schema/resolvers/rewards.ts create mode 100644 webapp/constants/badges.js diff --git a/backend/Dockerfile b/backend/Dockerfile index 40b78225a..e1c244069 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -23,6 +23,7 @@ COPY . . ONBUILD COPY ./branding/constants/ src/config/tmp ONBUILD RUN tools/replace-constants.sh ONBUILD COPY ./branding/email/ src/middleware/helpers/email/ +ONBUILD COPY ./branding/data/ src/db/data ONBUILD RUN yarn install --production=false --frozen-lockfile --non-interactive ONBUILD RUN yarn run build ONBUILD RUN mkdir /build diff --git a/backend/README.md b/backend/README.md index bfc875d95..7d8bbfb15 100644 --- a/backend/README.md +++ b/backend/README.md @@ -120,6 +120,20 @@ When using `CATEGORIES_ACTIVE=true` you also want to seed the categories with: yarn db:data:categories ``` +### Branding Data + +You might need to seed some branding specific data into the database. + +To do so, run: + +```sh +# in backend with database running (In docker or local) +yarn db:data:branding + +# for docker +docker exec backend yarn db:data:branding +``` + ### Seed Data For a predefined set of test data you can seed the database with: diff --git a/backend/branding/data/.gitkeep b/backend/branding/data/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/backend/package.json b/backend/package.json index b6cadf0b9..f0a4ef947 100644 --- a/backend/package.json +++ b/backend/package.json @@ -18,6 +18,8 @@ "db:reset:withmigrations": "ts-node --require tsconfig-paths/register src/db/reset-with-migrations.ts", "db:seed": "ts-node --require tsconfig-paths/register src/db/seed.ts", "db:data:admin": "ts-node --require tsconfig-paths/register src/db/admin.ts", + "db:data:badges": "ts-node --require tsconfig-paths/register src/db/badges.ts", + "db:data:branding": "ts-node --require tsconfig-paths/register src/db/data-production.ts", "db:data:categories": "ts-node --require tsconfig-paths/register src/db/categories.ts", "db:migrate": "migrate --compiler 'ts:./src/db/compiler.ts' --migrations-dir ./src/db/migrations --store ./src/db/migrate/store.ts", "db:migrate:create": "migrate --compiler 'ts:./src/db/compiler.ts' --migrations-dir ./src/db/migrations --template-file ./src/db/migrate/template.ts --date-format 'yyyymmddHHmmss' create", diff --git a/backend/public/img/badges/indiegogo_en_bear.svg b/backend/public/img/badges/trophy_blue_bear.svg similarity index 100% rename from backend/public/img/badges/indiegogo_en_bear.svg rename to backend/public/img/badges/trophy_blue_bear.svg diff --git a/backend/public/img/badges/indiegogo_en_panda.svg b/backend/public/img/badges/trophy_blue_panda.svg similarity index 100% rename from backend/public/img/badges/indiegogo_en_panda.svg rename to backend/public/img/badges/trophy_blue_panda.svg diff --git a/backend/public/img/badges/indiegogo_en_rabbit.svg b/backend/public/img/badges/trophy_blue_rabbit.svg similarity index 100% rename from backend/public/img/badges/indiegogo_en_rabbit.svg rename to backend/public/img/badges/trophy_blue_rabbit.svg diff --git a/backend/public/img/badges/indiegogo_en_racoon.svg b/backend/public/img/badges/trophy_blue_racoon.svg similarity index 100% rename from backend/public/img/badges/indiegogo_en_racoon.svg rename to backend/public/img/badges/trophy_blue_racoon.svg diff --git a/backend/public/img/badges/indiegogo_en_rhino.svg b/backend/public/img/badges/trophy_blue_rhino.svg similarity index 100% rename from backend/public/img/badges/indiegogo_en_rhino.svg rename to backend/public/img/badges/trophy_blue_rhino.svg diff --git a/backend/public/img/badges/indiegogo_en_tiger.svg b/backend/public/img/badges/trophy_blue_tiger.svg similarity index 100% rename from backend/public/img/badges/indiegogo_en_tiger.svg rename to backend/public/img/badges/trophy_blue_tiger.svg diff --git a/backend/public/img/badges/indiegogo_en_turtle.svg b/backend/public/img/badges/trophy_blue_turtle.svg similarity index 100% rename from backend/public/img/badges/indiegogo_en_turtle.svg rename to backend/public/img/badges/trophy_blue_turtle.svg diff --git a/backend/public/img/badges/indiegogo_en_whale.svg b/backend/public/img/badges/trophy_blue_whale.svg similarity index 100% rename from backend/public/img/badges/indiegogo_en_whale.svg rename to backend/public/img/badges/trophy_blue_whale.svg diff --git a/backend/public/img/badges/indiegogo_en_wolf.svg b/backend/public/img/badges/trophy_blue_wolf.svg similarity index 100% rename from backend/public/img/badges/indiegogo_en_wolf.svg rename to backend/public/img/badges/trophy_blue_wolf.svg diff --git a/backend/public/img/badges/fundraisingbox_de_airship.svg b/backend/public/img/badges/trophy_green_airship.svg similarity index 100% rename from backend/public/img/badges/fundraisingbox_de_airship.svg rename to backend/public/img/badges/trophy_green_airship.svg diff --git a/backend/public/img/badges/fundraisingbox_de_alienship.svg b/backend/public/img/badges/trophy_green_alienship.svg similarity index 100% rename from backend/public/img/badges/fundraisingbox_de_alienship.svg rename to backend/public/img/badges/trophy_green_alienship.svg diff --git a/backend/public/img/badges/fundraisingbox_de_balloon.svg b/backend/public/img/badges/trophy_green_balloon.svg similarity index 100% rename from backend/public/img/badges/fundraisingbox_de_balloon.svg rename to backend/public/img/badges/trophy_green_balloon.svg diff --git a/backend/public/img/badges/wooold_de_bee.svg b/backend/public/img/badges/trophy_green_bee.svg similarity index 100% rename from backend/public/img/badges/wooold_de_bee.svg rename to backend/public/img/badges/trophy_green_bee.svg diff --git a/backend/public/img/badges/fundraisingbox_de_bigballoon.svg b/backend/public/img/badges/trophy_green_bigballoon.svg similarity index 100% rename from backend/public/img/badges/fundraisingbox_de_bigballoon.svg rename to backend/public/img/badges/trophy_green_bigballoon.svg diff --git a/backend/public/img/badges/wooold_de_butterfly.svg b/backend/public/img/badges/trophy_green_butterfly.svg similarity index 100% rename from backend/public/img/badges/wooold_de_butterfly.svg rename to backend/public/img/badges/trophy_green_butterfly.svg diff --git a/backend/public/img/badges/fundraisingbox_de_crane.svg b/backend/public/img/badges/trophy_green_crane.svg similarity index 100% rename from backend/public/img/badges/fundraisingbox_de_crane.svg rename to backend/public/img/badges/trophy_green_crane.svg diff --git a/backend/public/img/badges/wooold_de_double_rainbow.svg b/backend/public/img/badges/trophy_green_doublerainbow.svg similarity index 100% rename from backend/public/img/badges/wooold_de_double_rainbow.svg rename to backend/public/img/badges/trophy_green_doublerainbow.svg diff --git a/backend/public/img/badges/wooold_de_end_of_rainbow.svg b/backend/public/img/badges/trophy_green_endrainbow.svg similarity index 100% rename from backend/public/img/badges/wooold_de_end_of_rainbow.svg rename to backend/public/img/badges/trophy_green_endrainbow.svg diff --git a/backend/public/img/badges/wooold_de_flower.svg b/backend/public/img/badges/trophy_green_flower.svg similarity index 100% rename from backend/public/img/badges/wooold_de_flower.svg rename to backend/public/img/badges/trophy_green_flower.svg diff --git a/backend/public/img/badges/fundraisingbox_de_glider.svg b/backend/public/img/badges/trophy_green_glider.svg similarity index 100% rename from backend/public/img/badges/fundraisingbox_de_glider.svg rename to backend/public/img/badges/trophy_green_glider.svg diff --git a/backend/public/img/badges/fundraisingbox_de_helicopter.svg b/backend/public/img/badges/trophy_green_helicopter.svg similarity index 100% rename from backend/public/img/badges/fundraisingbox_de_helicopter.svg rename to backend/public/img/badges/trophy_green_helicopter.svg diff --git a/backend/public/img/badges/wooold_de_lifetree.svg b/backend/public/img/badges/trophy_green_lifetree.svg similarity index 100% rename from backend/public/img/badges/wooold_de_lifetree.svg rename to backend/public/img/badges/trophy_green_lifetree.svg diff --git a/backend/public/img/badges/wooold_de_magic_rainbow.svg b/backend/public/img/badges/trophy_green_magicrainbow.svg similarity index 100% rename from backend/public/img/badges/wooold_de_magic_rainbow.svg rename to backend/public/img/badges/trophy_green_magicrainbow.svg diff --git a/backend/public/img/badges/fundraisingbox_de_starter.svg b/backend/public/img/badges/trophy_green_starter.svg similarity index 100% rename from backend/public/img/badges/fundraisingbox_de_starter.svg rename to backend/public/img/badges/trophy_green_starter.svg diff --git a/backend/public/img/badges/wooold_de_super_founder.svg b/backend/public/img/badges/trophy_green_superfounder.svg similarity index 100% rename from backend/public/img/badges/wooold_de_super_founder.svg rename to backend/public/img/badges/trophy_green_superfounder.svg diff --git a/backend/public/img/badges/user_role_admin.svg b/backend/public/img/badges/verification_red_admin.svg similarity index 100% rename from backend/public/img/badges/user_role_admin.svg rename to backend/public/img/badges/verification_red_admin.svg diff --git a/backend/public/img/badges/user_role_developer.svg b/backend/public/img/badges/verification_red_developer.svg similarity index 100% rename from backend/public/img/badges/user_role_developer.svg rename to backend/public/img/badges/verification_red_developer.svg diff --git a/backend/public/img/badges/user_role_moderator.svg b/backend/public/img/badges/verification_red_moderator.svg similarity index 100% rename from backend/public/img/badges/user_role_moderator.svg rename to backend/public/img/badges/verification_red_moderator.svg diff --git a/backend/src/constants/badges.ts b/backend/src/constants/badges.ts new file mode 100644 index 000000000..bccebb39a --- /dev/null +++ b/backend/src/constants/badges.ts @@ -0,0 +1,2 @@ +// this file is duplicated in `backend/src/constants/badges` and `webapp/constants/badges.js` +export const TROPHY_BADGES_SELECTED_MAX = 9 diff --git a/backend/src/db/badges.ts b/backend/src/db/badges.ts new file mode 100644 index 000000000..a77f08731 --- /dev/null +++ b/backend/src/db/badges.ts @@ -0,0 +1,13 @@ +import { getNeode } from './neo4j' +import { trophies, verification } from './seed/badges' + +// eslint-disable-next-line import/newline-after-import +;(async function () { + const neode = getNeode() + try { + await trophies() + await verification() + } finally { + await neode.close() + } +})() diff --git a/backend/src/db/data-branding.ts b/backend/src/db/data-branding.ts new file mode 100644 index 000000000..ff84aca60 --- /dev/null +++ b/backend/src/db/data-branding.ts @@ -0,0 +1,17 @@ +import { readdir } from 'node:fs/promises' +import path from 'node:path' + +const dataFolder = path.join(__dirname, 'data/') + +;(async function () { + const files = await readdir(dataFolder) + files.forEach(async (file) => { + if (file.slice(0, -3).endsWith('-branding')) { + const importedModule = await import(path.join(dataFolder, file)) + if (!importedModule.default) { + throw new Error('Your data file must export a default function') + } + await importedModule.default() + } + }) +})() diff --git a/backend/src/db/data/.gitkeep b/backend/src/db/data/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/backend/src/db/migrations/20250414220436-delete-old-badges.ts b/backend/src/db/migrations/20250414220436-delete-old-badges.ts new file mode 100644 index 000000000..db3a53d33 --- /dev/null +++ b/backend/src/db/migrations/20250414220436-delete-old-badges.ts @@ -0,0 +1,49 @@ +import { getDriver } from '@db/neo4j' + +export const description = '' + +export async function up(next) { + const driver = getDriver() + const session = driver.session() + const transaction = session.beginTransaction() + + try { + // Implement your migration here. + await transaction.run(` + MATCH (badge:Badge) + DETACH DELETE badge + `) + await transaction.commit() + } catch (error) { + // eslint-disable-next-line no-console + console.log(error) + await transaction.rollback() + // eslint-disable-next-line no-console + console.log('rolled back') + throw new Error(error) + } finally { + session.close() + } +} + +export async function down(next) { + const driver = getDriver() + const session = driver.session() + const transaction = session.beginTransaction() + + try { + // cannot be rolled back + // Implement your migration here. + // await transaction.run(``) + // await transaction.commit() + } catch (error) { + // eslint-disable-next-line no-console + console.log(error) + await transaction.rollback() + // eslint-disable-next-line no-console + console.log('rolled back') + throw new Error(error) + } finally { + session.close() + } +} diff --git a/backend/src/db/seed.ts b/backend/src/db/seed.ts index 34a6ebb03..5f918235e 100644 --- a/backend/src/db/seed.ts +++ b/backend/src/db/seed.ts @@ -18,6 +18,7 @@ import createServer from '@src/server' import Factory from './factories' import { getNeode, getDriver } from './neo4j' +import { trophies, verification } from './seed/badges' if (CONFIG.PRODUCTION && !CONFIG.PRODUCTION_DB_CLEAN_ALLOW) { throw new Error(`You cannot seed the database in a non-staging and real production environment!`) @@ -124,32 +125,28 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] await Hamburg.relateTo(Germany, 'isIn') await Paris.relateTo(France, 'isIn') - // badges - const racoon = await Factory.build('badge', { - id: 'indiegogo_en_racoon', - icon: '/img/badges/indiegogo_en_racoon.svg', - }) - const rabbit = await Factory.build('badge', { - id: 'indiegogo_en_rabbit', - icon: '/img/badges/indiegogo_en_rabbit.svg', - }) - const wolf = await Factory.build('badge', { - id: 'indiegogo_en_wolf', - icon: '/img/badges/indiegogo_en_wolf.svg', - }) - const bear = await Factory.build('badge', { - id: 'indiegogo_en_bear', - icon: '/img/badges/indiegogo_en_bear.svg', - }) - const turtle = await Factory.build('badge', { - id: 'indiegogo_en_turtle', - icon: '/img/badges/indiegogo_en_turtle.svg', - }) - const rhino = await Factory.build('badge', { - id: 'indiegogo_en_rhino', - icon: '/img/badges/indiegogo_en_rhino.svg', - }) + const { + trophyAirship, + trophyBee, + trophyStarter, + trophyFlower, + trophyPanda, + trophyTiger, + trophyAlienship, + trophyBalloon, + trophyMagicrainbow, + trophySuperfounder, + trophyBigballoon, + trophyLifetree, + trophyRacoon, + trophyRhino, + trophyWolf, + trophyTurtle, + trophyBear, + trophyRabbit, + } = await trophies() + const { verificationAdmin, verificationModerator, verificationDeveloper } = await verification() // users const peterLustig = await Factory.build( 'user', @@ -243,14 +240,50 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] await jennyRostock.relateTo(Paris, 'isIn') await huey.relateTo(Paris, 'isIn') - await peterLustig.relateTo(racoon, 'rewarded') - await peterLustig.relateTo(rhino, 'rewarded') - await peterLustig.relateTo(wolf, 'rewarded') - await bobDerBaumeister.relateTo(racoon, 'rewarded') - await bobDerBaumeister.relateTo(turtle, 'rewarded') - await jennyRostock.relateTo(bear, 'rewarded') - await dagobert.relateTo(rabbit, 'rewarded') + // badges + await peterLustig.relateTo(trophyRacoon, 'rewarded') + await peterLustig.relateTo(trophyRhino, 'rewarded') + await peterLustig.relateTo(trophyWolf, 'rewarded') + await peterLustig.relateTo(trophyAirship, 'rewarded') + await peterLustig.relateTo(verificationAdmin, 'verifies') + await peterLustig.relateTo(trophyRacoon, 'selected', { slot: 0 }) + await peterLustig.relateTo(trophyRhino, 'selected', { slot: 1 }) + await peterLustig.relateTo(trophyAirship, 'selected', { slot: 5 }) + await bobDerBaumeister.relateTo(trophyRacoon, 'rewarded') + await bobDerBaumeister.relateTo(trophyTurtle, 'rewarded') + await bobDerBaumeister.relateTo(trophyBee, 'rewarded') + await bobDerBaumeister.relateTo(verificationModerator, 'verifies') + await bobDerBaumeister.relateTo(trophyRacoon, 'selected', { slot: 1 }) + await bobDerBaumeister.relateTo(trophyTurtle, 'selected', { slot: 2 }) + + await jennyRostock.relateTo(trophyBear, 'rewarded') + await jennyRostock.relateTo(trophyStarter, 'rewarded') + await jennyRostock.relateTo(trophyFlower, 'rewarded') + await jennyRostock.relateTo(trophyBear, 'selected', { slot: 0 }) + await jennyRostock.relateTo(trophyStarter, 'selected', { slot: 1 }) + await jennyRostock.relateTo(trophyFlower, 'selected', { slot: 2 }) + + await huey.relateTo(trophyPanda, 'rewarded') + await huey.relateTo(trophyTiger, 'rewarded') + await huey.relateTo(trophyAlienship, 'rewarded') + await huey.relateTo(trophyBalloon, 'rewarded') + await huey.relateTo(trophyMagicrainbow, 'rewarded') + await huey.relateTo(trophySuperfounder, 'rewarded') + await huey.relateTo(verificationDeveloper, 'verifies') + await huey.relateTo(trophyPanda, 'selected', { slot: 0 }) + await huey.relateTo(trophyTiger, 'selected', { slot: 1 }) + await huey.relateTo(trophyAlienship, 'selected', { slot: 2 }) + + await dewey.relateTo(trophyBigballoon, 'rewarded') + await dewey.relateTo(trophyLifetree, 'rewarded') + await dewey.relateTo(trophyBigballoon, 'selected', { slot: 7 }) + await dewey.relateTo(trophyLifetree, 'selected', { slot: 8 }) + + await louie.relateTo(trophyRabbit, 'rewarded') + await louie.relateTo(trophyRabbit, 'selected', { slot: 4 }) + + // Friends await peterLustig.relateTo(bobDerBaumeister, 'friends') await peterLustig.relateTo(jennyRostock, 'friends') await bobDerBaumeister.relateTo(jennyRostock, 'friends') diff --git a/backend/src/db/seed/badges.ts b/backend/src/db/seed/badges.ts new file mode 100644 index 000000000..2ec4bb21b --- /dev/null +++ b/backend/src/db/seed/badges.ts @@ -0,0 +1,185 @@ +import Factory from '@db/factories' + +export const trophies = async () => { + return { + // Blue Animals + trophyBear: await Factory.build('badge', { + id: 'trophy_bear', + type: 'trophy', + description: 'You earned a Bear', + icon: '/img/badges/trophy_blue_bear.svg', + }), + trophyPanda: await Factory.build('badge', { + id: 'trophy_panda', + type: 'trophy', + description: 'You earned a Panda', + icon: '/img/badges/trophy_blue_panda.svg', + }), + trophyRabbit: await Factory.build('badge', { + id: 'trophy_rabbit', + type: 'trophy', + description: 'You earned a Rabbit', + icon: '/img/badges/trophy_blue_rabbit.svg', + }), + trophyRacoon: await Factory.build('badge', { + id: 'trophy_racoon', + type: 'trophy', + description: 'You earned a Racoon', + icon: '/img/badges/trophy_blue_racoon.svg', + }), + trophyRhino: await Factory.build('badge', { + id: 'trophy_rhino', + type: 'trophy', + description: 'You earned a Rhino', + icon: '/img/badges/trophy_blue_rhino.svg', + }), + trophyTiger: await Factory.build('badge', { + id: 'trophy_tiger', + type: 'trophy', + description: 'You earned a Tiger', + icon: '/img/badges/trophy_blue_tiger.svg', + }), + trophyTurtle: await Factory.build('badge', { + id: 'trophy_turtle', + type: 'trophy', + description: 'You earned a Turtle', + icon: '/img/badges/trophy_blue_turtle.svg', + }), + trophyWhale: await Factory.build('badge', { + id: 'trophy_whale', + type: 'trophy', + description: 'You earned a Whale', + icon: '/img/badges/trophy_blue_whale.svg', + }), + trophyWolf: await Factory.build('badge', { + id: 'trophy_wolf', + type: 'trophy', + description: 'You earned a Wolf', + icon: '/img/badges/trophy_blue_wolf.svg', + }), + // Green Transports + trophyAirship: await Factory.build('badge', { + id: 'trophy_airship', + type: 'trophy', + description: 'You earned an Airship', + icon: '/img/badges/trophy_green_airship.svg', + }), + trophyAlienship: await Factory.build('badge', { + id: 'trophy_alienship', + type: 'trophy', + description: 'You earned an Alienship', + icon: '/img/badges/trophy_green_alienship.svg', + }), + trophyBalloon: await Factory.build('badge', { + id: 'trophy_balloon', + type: 'trophy', + description: 'You earned a Balloon', + icon: '/img/badges/trophy_green_balloon.svg', + }), + trophyBigballoon: await Factory.build('badge', { + id: 'trophy_bigballoon', + type: 'trophy', + description: 'You earned a Big Balloon', + icon: '/img/badges/trophy_green_bigballoon.svg', + }), + trophyCrane: await Factory.build('badge', { + id: 'trophy_crane', + type: 'trophy', + description: 'You earned a Crane', + icon: '/img/badges/trophy_green_crane.svg', + }), + trophyGlider: await Factory.build('badge', { + id: 'trophy_glider', + type: 'trophy', + description: 'You earned a Glider', + icon: '/img/badges/trophy_green_glider.svg', + }), + trophyHelicopter: await Factory.build('badge', { + id: 'trophy_helicopter', + type: 'trophy', + description: 'You earned a Helicopter', + icon: '/img/badges/trophy_green_helicopter.svg', + }), + // Green Animals + trophyBee: await Factory.build('badge', { + id: 'trophy_bee', + type: 'trophy', + description: 'You earned a Bee', + icon: '/img/badges/trophy_green_bee.svg', + }), + trophyButterfly: await Factory.build('badge', { + id: 'trophy_butterfly', + type: 'trophy', + description: 'You earned a Butterfly', + icon: '/img/badges/trophy_green_butterfly.svg', + }), + // Green Plants + trophyFlower: await Factory.build('badge', { + id: 'trophy_flower', + type: 'trophy', + description: 'You earned a Flower', + icon: '/img/badges/trophy_green_flower.svg', + }), + trophyLifetree: await Factory.build('badge', { + id: 'trophy_lifetree', + type: 'trophy', + description: 'You earned the tree of life', + icon: '/img/badges/trophy_green_lifetree.svg', + }), + // Green Misc + trophyDoublerainbow: await Factory.build('badge', { + id: 'trophy_doublerainbow', + type: 'trophy', + description: 'You earned the Double Rainbow', + icon: '/img/badges/trophy_green_doublerainbow.svg', + }), + trophyEndrainbow: await Factory.build('badge', { + id: 'trophy_endrainbow', + type: 'trophy', + description: 'You earned the End of the Rainbow', + icon: '/img/badges/trophy_green_endrainbow.svg', + }), + trophyMagicrainbow: await Factory.build('badge', { + id: 'trophy_magicrainbow', + type: 'trophy', + description: 'You earned the Magic Rainbow', + icon: '/img/badges/trophy_green_magicrainbow.svg', + }), + trophyStarter: await Factory.build('badge', { + id: 'trophy_starter', + type: 'trophy', + description: 'You earned the Starter Badge', + icon: '/img/badges/trophy_green_starter.svg', + }), + trophySuperfounder: await Factory.build('badge', { + id: 'trophy_superfounder', + type: 'trophy', + description: 'You earned the Super Founder Badge', + icon: '/img/badges/trophy_green_superfounder.svg', + }), + } +} + +export const verification = async () => { + return { + // Red Role + verificationModerator: await Factory.build('badge', { + id: 'verification_moderator', + type: 'verification', + description: 'You are a Moderator', + icon: '/img/badges/verification_red_moderator.svg', + }), + verificationAdmin: await Factory.build('badge', { + id: 'verification_admin', + type: 'verification', + description: 'You are an Administrator', + icon: '/img/badges/verification_red_admin.svg', + }), + verificationDeveloper: await Factory.build('badge', { + id: 'verification_developer', + type: 'verification', + description: 'You are a Developer', + icon: '/img/badges/verification_red_developer.svg', + }), + } +} diff --git a/backend/src/middleware/permissionsMiddleware.ts b/backend/src/middleware/permissionsMiddleware.ts index 0f2b71678..aa48d9d10 100644 --- a/backend/src/middleware/permissionsMiddleware.ts +++ b/backend/src/middleware/permissionsMiddleware.ts @@ -429,10 +429,9 @@ export default shield( CreateSocialMedia: isAuthenticated, UpdateSocialMedia: isMySocialMedia, DeleteSocialMedia: isMySocialMedia, - // AddBadgeRewarded: isAdmin, - // RemoveBadgeRewarded: isAdmin, - reward: isAdmin, - unreward: isAdmin, + setVerificationBadge: isAdmin, + rewardTrophyBadge: isAdmin, + revokeBadge: isAdmin, followUser: isAuthenticated, unfollowUser: isAuthenticated, shout: isAuthenticated, @@ -469,6 +468,8 @@ export default shield( toggleObservePost: isAuthenticated, muteGroup: and(isAuthenticated, isMemberOfGroup), unmuteGroup: and(isAuthenticated, isMemberOfGroup), + setTrophyBadgeSelected: isAuthenticated, + resetTrophyBadgesSelected: isAuthenticated, }, User: { email: or(isMyOwn, isAdmin), diff --git a/backend/src/models/Badge.ts b/backend/src/models/Badge.ts index 9c4831041..e8d61cb42 100644 --- a/backend/src/models/Badge.ts +++ b/backend/src/models/Badge.ts @@ -1,7 +1,7 @@ export default { id: { type: 'string', primary: true, lowercase: true }, - status: { type: 'string', valid: ['permanent', 'temporary'] }, - type: { type: 'string', valid: ['role', 'crowdfunding'] }, + type: { type: 'string', valid: ['verification', 'trophy'] }, icon: { type: 'string', required: true }, + description: { type: 'string', required: true }, createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, } diff --git a/backend/src/models/User.ts b/backend/src/models/User.ts index 754f879a4..77a37c3c1 100644 --- a/backend/src/models/User.ts +++ b/backend/src/models/User.ts @@ -52,6 +52,24 @@ export default { target: 'Badge', direction: 'in', }, + selected: { + type: 'relationship', + relationship: 'SELECTED', + target: 'Badge', + direction: 'out', + properties: { + slot: { + type: 'int', + required: true, + }, + }, + }, + verifies: { + type: 'relationship', + relationship: 'VERIFIES', + target: 'Badge', + direction: 'in', + }, invitedBy: { type: 'relationship', relationship: 'INVITED', target: 'User', direction: 'in' }, lastActiveAt: { type: 'string', isoDate: true }, lastOnlineStatus: { type: 'string' }, diff --git a/backend/src/schema/resolvers/badges.spec.ts b/backend/src/schema/resolvers/badges.spec.ts new file mode 100644 index 000000000..c9d297547 --- /dev/null +++ b/backend/src/schema/resolvers/badges.spec.ts @@ -0,0 +1,667 @@ +import { createTestClient } from 'apollo-server-testing' +import gql from 'graphql-tag' + +import Factory, { cleanDatabase } from '@db/factories' +import { getNeode, getDriver } from '@db/neo4j' +import createServer from '@src/server' + +const driver = getDriver() +const instance = getNeode() + +let authenticatedUser, regularUser, administrator, moderator, badge, verification, query, mutate + +describe('Badges', () => { + beforeAll(async () => { + await cleanDatabase() + + const { server } = createServer({ + context: () => { + return { + driver, + neode: instance, + user: authenticatedUser, + } + }, + }) + query = createTestClient(server).query + mutate = createTestClient(server).mutate + }) + + afterAll(async () => { + await cleanDatabase() + driver.close() + }) + + beforeEach(async () => { + regularUser = await Factory.build( + 'user', + { + id: 'regular-user-id', + role: 'user', + }, + { + email: 'user@example.org', + password: '1234', + }, + ) + moderator = await Factory.build( + 'user', + { + id: 'moderator-id', + role: 'moderator', + }, + { + email: 'moderator@example.org', + }, + ) + administrator = await Factory.build( + 'user', + { + id: 'admin-id', + role: 'admin', + }, + { + email: 'admin@example.org', + }, + ) + badge = await Factory.build('badge', { + id: 'trophy_rhino', + type: 'trophy', + description: 'You earned a rhino', + icon: '/img/badges/trophy_blue_rhino.svg', + }) + + verification = await Factory.build('badge', { + id: 'verification_moderator', + type: 'verification', + description: 'You are a moderator', + icon: '/img/badges/verification_red_moderator.svg', + }) + }) + + // TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543 + afterEach(async () => { + await cleanDatabase() + }) + + describe('setVerificationBadge', () => { + const variables = { + badgeId: 'verification_moderator', + userId: 'regular-user-id', + } + + const setVerificationBadgeMutation = gql` + mutation ($badgeId: ID!, $userId: ID!) { + setVerificationBadge(badgeId: $badgeId, userId: $userId) { + id + badgeVerification { + id + } + badgeTrophies { + id + } + } + } + ` + + describe('unauthenticated', () => { + it('throws authorization error', async () => { + authenticatedUser = null + await expect( + mutate({ mutation: setVerificationBadgeMutation, variables }), + ).resolves.toMatchObject({ + data: { setVerificationBadge: null }, + errors: [{ message: 'Not Authorized!' }], + }) + }) + }) + + describe('authenticated as moderator', () => { + beforeEach(async () => { + authenticatedUser = moderator.toJson() + }) + + describe('rewards badge to user', () => { + it('throws authorization error', async () => { + await expect( + mutate({ mutation: setVerificationBadgeMutation, variables }), + ).resolves.toMatchObject({ + data: { setVerificationBadge: null }, + errors: [{ message: 'Not Authorized!' }], + }) + }) + }) + }) + + describe('authenticated as admin', () => { + beforeEach(async () => { + authenticatedUser = await administrator.toJson() + }) + + describe('badge for id does not exist', () => { + it('rejects with an informative error message', async () => { + await expect( + mutate({ + mutation: setVerificationBadgeMutation, + variables: { userId: 'regular-user-id', badgeId: 'non-existent-badge-id' }, + }), + ).resolves.toMatchObject({ + data: { setVerificationBadge: null }, + errors: [ + { + message: + 'Error: Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.', + }, + ], + }) + }) + }) + + describe('non-existent user', () => { + it('rejects with a telling error message', async () => { + await expect( + mutate({ + mutation: setVerificationBadgeMutation, + variables: { userId: 'non-existent-user-id', badgeId: 'verification_moderator' }, + }), + ).resolves.toMatchObject({ + data: { setVerificationBadge: null }, + errors: [ + { + message: + 'Error: Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.', + }, + ], + }) + }) + }) + + describe('badge is not a verification badge', () => { + it('rejects with a telling error message', async () => { + await expect( + mutate({ + mutation: setVerificationBadgeMutation, + variables: { userId: 'regular-user-id', badgeId: 'trophy_rhino' }, + }), + ).resolves.toMatchObject({ + data: { setVerificationBadge: null }, + errors: [ + { + message: + 'Error: Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.', + }, + ], + }) + }) + }) + + it('rewards a verification badge to the user', async () => { + const expected = { + data: { + setVerificationBadge: { + id: 'regular-user-id', + badgeVerification: { id: 'verification_moderator' }, + badgeTrophies: [], + }, + }, + errors: undefined, + } + await expect( + mutate({ mutation: setVerificationBadgeMutation, variables }), + ).resolves.toMatchObject(expected) + }) + + it('overrides the existing verification if a second verification badge is rewarded to the same user', async () => { + await Factory.build('badge', { + id: 'verification_admin', + type: 'verification', + description: 'You are an admin', + icon: '/img/badges/verification_red_admin.svg', + }) + const expected = { + data: { + setVerificationBadge: { + id: 'regular-user-id', + badgeVerification: { id: 'verification_admin' }, + badgeTrophies: [], + }, + }, + errors: undefined, + } + await mutate({ + mutation: setVerificationBadgeMutation, + variables: { + userId: 'regular-user-id', + badgeId: 'verification_moderator', + }, + }) + await expect( + mutate({ + mutation: setVerificationBadgeMutation, + variables: { + userId: 'regular-user-id', + badgeId: 'verification_admin', + }, + }), + ).resolves.toMatchObject(expected) + }) + + it('rewards the same verification badge as well to another user', async () => { + const expected = { + data: { + setVerificationBadge: { + id: 'regular-user-2-id', + badgeVerification: { id: 'verification_moderator' }, + badgeTrophies: [], + }, + }, + errors: undefined, + } + await Factory.build( + 'user', + { + id: 'regular-user-2-id', + }, + { + email: 'regular2@email.com', + }, + ) + await mutate({ + mutation: setVerificationBadgeMutation, + variables, + }) + await expect( + mutate({ + mutation: setVerificationBadgeMutation, + variables: { + userId: 'regular-user-2-id', + badgeId: 'verification_moderator', + }, + }), + ).resolves.toMatchObject(expected) + }) + }) + }) + + describe('rewardTrophyBadge', () => { + const variables = { + badgeId: 'trophy_rhino', + userId: 'regular-user-id', + } + + const rewardTrophyBadgeMutation = gql` + mutation ($badgeId: ID!, $userId: ID!) { + rewardTrophyBadge(badgeId: $badgeId, userId: $userId) { + id + badgeVerification { + id + } + badgeTrophies { + id + } + } + } + ` + + describe('unauthenticated', () => { + it('throws authorization error', async () => { + authenticatedUser = null + await expect( + mutate({ mutation: rewardTrophyBadgeMutation, variables }), + ).resolves.toMatchObject({ + data: { rewardTrophyBadge: null }, + errors: [{ message: 'Not Authorized!' }], + }) + }) + }) + + describe('authenticated as moderator', () => { + beforeEach(async () => { + authenticatedUser = moderator.toJson() + }) + + describe('rewards badge to user', () => { + it('throws authorization error', async () => { + await expect( + mutate({ mutation: rewardTrophyBadgeMutation, variables }), + ).resolves.toMatchObject({ + data: { rewardTrophyBadge: null }, + errors: [{ message: 'Not Authorized!' }], + }) + }) + }) + }) + + describe('authenticated as admin', () => { + beforeEach(async () => { + authenticatedUser = await administrator.toJson() + }) + + describe('badge for id does not exist', () => { + it('rejects with an informative error message', async () => { + await expect( + mutate({ + mutation: rewardTrophyBadgeMutation, + variables: { userId: 'regular-user-id', badgeId: 'non-existent-badge-id' }, + }), + ).resolves.toMatchObject({ + data: { rewardTrophyBadge: null }, + errors: [ + { + message: + 'Error: Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.', + }, + ], + }) + }) + }) + + describe('non-existent user', () => { + it('rejects with a telling error message', async () => { + await expect( + mutate({ + mutation: rewardTrophyBadgeMutation, + variables: { userId: 'non-existent-user-id', badgeId: 'trophy_rhino' }, + }), + ).resolves.toMatchObject({ + data: { rewardTrophyBadge: null }, + errors: [ + { + message: + 'Error: Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.', + }, + ], + }) + }) + }) + + describe('badge is a verification Badge', () => { + it('rejects with a telling error message', async () => { + await expect( + mutate({ + mutation: rewardTrophyBadgeMutation, + variables: { userId: 'regular-user-id', badgeId: 'verification_moderator' }, + }), + ).resolves.toMatchObject({ + data: { rewardTrophyBadge: null }, + errors: [ + { + message: + 'Error: Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.', + }, + ], + }) + }) + }) + + it('rewards a badge to the user', async () => { + const expected = { + data: { + rewardTrophyBadge: { + id: 'regular-user-id', + badgeVerification: null, + badgeTrophies: [{ id: 'trophy_rhino' }], + }, + }, + errors: undefined, + } + await expect( + mutate({ mutation: rewardTrophyBadgeMutation, variables }), + ).resolves.toMatchObject(expected) + }) + + it('rewards a second different badge to the same user', async () => { + await Factory.build('badge', { + id: 'trophy_racoon', + type: 'trophy', + description: 'You earned a racoon', + icon: '/img/badges/trophy_blue_racoon.svg', + }) + const trophies = [{ id: 'trophy_racoon' }, { id: 'trophy_rhino' }] + const expected = { + data: { + rewardTrophyBadge: { + id: 'regular-user-id', + badgeTrophies: expect.arrayContaining(trophies), + }, + }, + errors: undefined, + } + await mutate({ + mutation: rewardTrophyBadgeMutation, + variables: { + userId: 'regular-user-id', + badgeId: 'trophy_rhino', + }, + }) + await expect( + mutate({ + mutation: rewardTrophyBadgeMutation, + variables: { + userId: 'regular-user-id', + badgeId: 'trophy_racoon', + }, + }), + ).resolves.toMatchObject(expected) + }) + + it('rewards the same badge as well to another user', async () => { + const expected = { + data: { + rewardTrophyBadge: { + id: 'regular-user-2-id', + badgeTrophies: [{ id: 'trophy_rhino' }], + }, + }, + errors: undefined, + } + await Factory.build( + 'user', + { + id: 'regular-user-2-id', + }, + { + email: 'regular2@email.com', + }, + ) + await mutate({ + mutation: rewardTrophyBadgeMutation, + variables, + }) + await expect( + mutate({ + mutation: rewardTrophyBadgeMutation, + variables: { + userId: 'regular-user-2-id', + badgeId: 'trophy_rhino', + }, + }), + ).resolves.toMatchObject(expected) + }) + + it('creates no duplicate reward relationships', async () => { + await mutate({ + mutation: rewardTrophyBadgeMutation, + variables, + }) + await mutate({ + mutation: rewardTrophyBadgeMutation, + variables, + }) + + const userQuery = gql` + { + User(id: "regular-user-id") { + badgeTrophiesCount + badgeTrophies { + id + } + } + } + ` + const expected = { + data: { User: [{ badgeTrophiesCount: 1, badgeTrophies: [{ id: 'trophy_rhino' }] }] }, + errors: undefined, + } + + await expect(query({ query: userQuery })).resolves.toMatchObject(expected) + }) + }) + }) + + describe('revokeBadge', () => { + const variables = { + badgeId: 'trophy_rhino', + userId: 'regular-user-id', + } + + beforeEach(async () => { + await regularUser.relateTo(badge, 'rewarded') + await regularUser.relateTo(verification, 'verifies') + }) + + const revokeBadgeMutation = gql` + mutation ($badgeId: ID!, $userId: ID!) { + revokeBadge(badgeId: $badgeId, userId: $userId) { + id + badgeVerification { + id + } + badgeTrophies { + id + } + } + } + ` + + describe('check test setup', () => { + it('user has one badge', async () => { + authenticatedUser = regularUser.toJson() + const userQuery = gql` + { + User(id: "regular-user-id") { + badgeTrophiesCount + badgeTrophies { + id + } + } + } + ` + const expected = { + data: { User: [{ badgeTrophiesCount: 1, badgeTrophies: [{ id: 'trophy_rhino' }] }] }, + errors: undefined, + } + await expect(query({ query: userQuery })).resolves.toMatchObject(expected) + }) + }) + + describe('unauthenticated', () => { + it('throws authorization error', async () => { + authenticatedUser = null + await expect(mutate({ mutation: revokeBadgeMutation, variables })).resolves.toMatchObject({ + data: { revokeBadge: null }, + errors: [{ message: 'Not Authorized!' }], + }) + }) + }) + + describe('authenticated moderator', () => { + beforeEach(async () => { + authenticatedUser = await moderator.toJson() + }) + + describe('removes badge from user', () => { + it('throws authorization error', async () => { + await expect(mutate({ mutation: revokeBadgeMutation, variables })).resolves.toMatchObject( + { + data: { revokeBadge: null }, + errors: [{ message: 'Not Authorized!' }], + }, + ) + }) + }) + }) + + describe('authenticated admin', () => { + beforeEach(async () => { + authenticatedUser = await administrator.toJson() + }) + + it('removes a badge from user', async () => { + await expect(mutate({ mutation: revokeBadgeMutation, variables })).resolves.toMatchObject({ + data: { + revokeBadge: { + id: 'regular-user-id', + badgeVerification: { id: 'verification_moderator' }, + badgeTrophies: [], + }, + }, + errors: undefined, + }) + }) + + it('does not crash when revoking multiple times', async () => { + await mutate({ mutation: revokeBadgeMutation, variables }) + await expect(mutate({ mutation: revokeBadgeMutation, variables })).resolves.toMatchObject({ + data: { + revokeBadge: { + id: 'regular-user-id', + badgeVerification: { id: 'verification_moderator' }, + badgeTrophies: [], + }, + }, + errors: undefined, + }) + }) + + it('removes a verification from user', async () => { + await expect( + mutate({ + mutation: revokeBadgeMutation, + variables: { + badgeId: 'verification_moderator', + userId: 'regular-user-id', + }, + }), + ).resolves.toMatchObject({ + data: { + revokeBadge: { + id: 'regular-user-id', + badgeVerification: null, + badgeTrophies: [{ id: 'trophy_rhino' }], + }, + }, + errors: undefined, + }) + }) + + it('does not crash when removing verification multiple times', async () => { + await mutate({ + mutation: revokeBadgeMutation, + variables: { + badgeId: 'verification_moderator', + userId: 'regular-user-id', + }, + }) + await expect( + mutate({ + mutation: revokeBadgeMutation, + variables: { + badgeId: 'verification_moderator', + userId: 'regular-user-id', + }, + }), + ).resolves.toMatchObject({ + data: { + revokeBadge: { + id: 'regular-user-id', + badgeVerification: null, + badgeTrophies: [{ id: 'trophy_rhino' }], + }, + }, + errors: undefined, + }) + }) + }) + }) +}) diff --git a/backend/src/schema/resolvers/badges.ts b/backend/src/schema/resolvers/badges.ts index d10d6b482..f0ae4f8e2 100644 --- a/backend/src/schema/resolvers/badges.ts +++ b/backend/src/schema/resolvers/badges.ts @@ -2,8 +2,119 @@ import { neo4jgraphql } from 'neo4j-graphql-js' export default { Query: { - Badge: async (object, args, context, resolveInfo) => { - return neo4jgraphql(object, args, context, resolveInfo) + Badge: async (object, args, context, resolveInfo) => + neo4jgraphql(object, args, context, resolveInfo), + }, + + Mutation: { + setVerificationBadge: async (_object, args, context, _resolveInfo) => { + const { + user: { id: currentUserId }, + } = context + const { badgeId, userId } = args + const session = context.driver.session() + + const writeTxResultPromise = session.writeTransaction(async (transaction) => { + const response = await transaction.run( + ` + MATCH (badge:Badge {id: $badgeId, type: 'verification'}), (user:User {id: $userId}) + OPTIONAL MATCH (:Badge {type: 'verification'})-[verify:VERIFIES]->(user) + DELETE verify + MERGE (badge)-[relation:VERIFIES {by: $currentUserId}]->(user) + RETURN relation, user {.*} + `, + { + badgeId, + userId, + currentUserId, + }, + ) + return { + relation: response.records.map((record) => record.get('relation'))[0], + user: response.records.map((record) => record.get('user'))[0], + } + }) + try { + const { relation, user } = await writeTxResultPromise + if (!relation) { + throw new Error( + 'Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.', + ) + } + return user + } catch (error) { + throw new Error(error) + } finally { + session.close() + } + }, + + rewardTrophyBadge: async (_object, args, context, _resolveInfo) => { + const { + user: { id: currentUserId }, + } = context + const { badgeId, userId } = args + const session = context.driver.session() + + const writeTxResultPromise = session.writeTransaction(async (transaction) => { + const response = await transaction.run( + ` + MATCH (badge:Badge {id: $badgeId, type: 'trophy'}), (user:User {id: $userId}) + MERGE (badge)-[relation:REWARDED {by: $currentUserId}]->(user) + RETURN relation, user {.*} + `, + { + badgeId, + userId, + currentUserId, + }, + ) + return { + relation: response.records.map((record) => record.get('relation'))[0], + user: response.records.map((record) => record.get('user'))[0], + } + }) + try { + const { relation, user } = await writeTxResultPromise + if (!relation) { + throw new Error( + 'Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.', + ) + } + return user + } catch (error) { + throw new Error(error) + } finally { + session.close() + } + }, + + revokeBadge: async (_object, args, context, _resolveInfo) => { + const { badgeId, userId } = args + const session = context.driver.session() + + const writeTxResultPromise = session.writeTransaction(async (transaction) => { + const response = await transaction.run( + ` + MATCH (user:User {id: $userId}) + OPTIONAL MATCH (badge:Badge {id: $badgeId})-[relation:REWARDED|VERIFIES]->(user) + DELETE relation + RETURN user {.*} + `, + { + badgeId, + userId, + }, + ) + return response.records.map((record) => record.get('user'))[0] + }) + try { + return await writeTxResultPromise + } catch (error) { + throw new Error(error) + } finally { + session.close() + } }, }, } diff --git a/backend/src/schema/resolvers/rewards.spec.ts b/backend/src/schema/resolvers/rewards.spec.ts deleted file mode 100644 index 2cfe122a0..000000000 --- a/backend/src/schema/resolvers/rewards.spec.ts +++ /dev/null @@ -1,352 +0,0 @@ -import { createTestClient } from 'apollo-server-testing' -import gql from 'graphql-tag' - -import Factory, { cleanDatabase } from '@db/factories' -import { getNeode, getDriver } from '@db/neo4j' -import createServer from '@src/server' - -const driver = getDriver() -const instance = getNeode() - -let authenticatedUser, regularUser, administrator, moderator, badge, query, mutate - -describe('rewards', () => { - const variables = { - from: 'indiegogo_en_rhino', - to: 'regular-user-id', - } - - beforeAll(async () => { - await cleanDatabase() - - const { server } = createServer({ - context: () => { - return { - driver, - neode: instance, - user: authenticatedUser, - } - }, - }) - query = createTestClient(server).query - mutate = createTestClient(server).mutate - }) - - afterAll(async () => { - await cleanDatabase() - driver.close() - }) - - beforeEach(async () => { - regularUser = await Factory.build( - 'user', - { - id: 'regular-user-id', - role: 'user', - }, - { - email: 'user@example.org', - password: '1234', - }, - ) - moderator = await Factory.build( - 'user', - { - id: 'moderator-id', - role: 'moderator', - }, - { - email: 'moderator@example.org', - }, - ) - administrator = await Factory.build( - 'user', - { - id: 'admin-id', - role: 'admin', - }, - { - email: 'admin@example.org', - }, - ) - badge = await Factory.build('badge', { - id: 'indiegogo_en_rhino', - type: 'crowdfunding', - status: 'permanent', - icon: '/img/badges/indiegogo_en_rhino.svg', - }) - }) - - // TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543 - afterEach(async () => { - await cleanDatabase() - }) - - describe('reward', () => { - const rewardMutation = gql` - mutation ($from: ID!, $to: ID!) { - reward(badgeKey: $from, userId: $to) { - id - badges { - id - } - } - } - ` - - describe('unauthenticated', () => { - it('throws authorization error', async () => { - authenticatedUser = null - await expect(mutate({ mutation: rewardMutation, variables })).resolves.toMatchObject({ - data: { reward: null }, - errors: [{ message: 'Not Authorized!' }], - }) - }) - }) - - describe('authenticated admin', () => { - beforeEach(async () => { - authenticatedUser = await administrator.toJson() - }) - - describe('badge for id does not exist', () => { - it('rejects with an informative error message', async () => { - await expect( - mutate({ - mutation: rewardMutation, - variables: { to: 'regular-user-id', from: 'non-existent-badge-id' }, - }), - ).resolves.toMatchObject({ - data: { reward: null }, - errors: [{ message: "Couldn't find a badge with that id" }], - }) - }) - }) - - describe('non-existent user', () => { - it('rejects with a telling error message', async () => { - await expect( - mutate({ - mutation: rewardMutation, - variables: { to: 'non-existent-user-id', from: 'indiegogo_en_rhino' }, - }), - ).resolves.toMatchObject({ - data: { reward: null }, - errors: [{ message: "Couldn't find a user with that id" }], - }) - }) - }) - - it('rewards a badge to user', async () => { - const expected = { - data: { - reward: { - id: 'regular-user-id', - badges: [{ id: 'indiegogo_en_rhino' }], - }, - }, - errors: undefined, - } - await expect(mutate({ mutation: rewardMutation, variables })).resolves.toMatchObject( - expected, - ) - }) - - it('rewards a second different badge to same user', async () => { - await Factory.build('badge', { - id: 'indiegogo_en_racoon', - icon: '/img/badges/indiegogo_en_racoon.svg', - }) - const badges = [{ id: 'indiegogo_en_racoon' }, { id: 'indiegogo_en_rhino' }] - const expected = { - data: { - reward: { - id: 'regular-user-id', - badges: expect.arrayContaining(badges), - }, - }, - errors: undefined, - } - await mutate({ - mutation: rewardMutation, - variables: { - to: 'regular-user-id', - from: 'indiegogo_en_rhino', - }, - }) - await expect( - mutate({ - mutation: rewardMutation, - variables: { - to: 'regular-user-id', - from: 'indiegogo_en_racoon', - }, - }), - ).resolves.toMatchObject(expected) - }) - - it('rewards the same badge as well to another user', async () => { - const expected = { - data: { - reward: { - id: 'regular-user-2-id', - badges: [{ id: 'indiegogo_en_rhino' }], - }, - }, - errors: undefined, - } - await Factory.build( - 'user', - { - id: 'regular-user-2-id', - }, - { - email: 'regular2@email.com', - }, - ) - await mutate({ - mutation: rewardMutation, - variables, - }) - await expect( - mutate({ - mutation: rewardMutation, - variables: { - to: 'regular-user-2-id', - from: 'indiegogo_en_rhino', - }, - }), - ).resolves.toMatchObject(expected) - }) - - it('creates no duplicate reward relationships', async () => { - await mutate({ - mutation: rewardMutation, - variables, - }) - await mutate({ - mutation: rewardMutation, - variables, - }) - - const userQuery = gql` - { - User(id: "regular-user-id") { - badgesCount - badges { - id - } - } - } - ` - const expected = { - data: { User: [{ badgesCount: 1, badges: [{ id: 'indiegogo_en_rhino' }] }] }, - errors: undefined, - } - - await expect(query({ query: userQuery })).resolves.toMatchObject(expected) - }) - }) - - describe('authenticated moderator', () => { - beforeEach(async () => { - authenticatedUser = moderator.toJson() - }) - - describe('rewards badge to user', () => { - it('throws authorization error', async () => { - await expect(mutate({ mutation: rewardMutation, variables })).resolves.toMatchObject({ - data: { reward: null }, - errors: [{ message: 'Not Authorized!' }], - }) - }) - }) - }) - }) - - describe('unreward', () => { - beforeEach(async () => { - await regularUser.relateTo(badge, 'rewarded') - }) - const expected = { - data: { unreward: { id: 'regular-user-id', badges: [] } }, - errors: undefined, - } - - const unrewardMutation = gql` - mutation ($from: ID!, $to: ID!) { - unreward(badgeKey: $from, userId: $to) { - id - badges { - id - } - } - } - ` - - describe('check test setup', () => { - it('user has one badge', async () => { - authenticatedUser = regularUser.toJson() - const userQuery = gql` - { - User(id: "regular-user-id") { - badgesCount - badges { - id - } - } - } - ` - const expected = { - data: { User: [{ badgesCount: 1, badges: [{ id: 'indiegogo_en_rhino' }] }] }, - errors: undefined, - } - await expect(query({ query: userQuery })).resolves.toMatchObject(expected) - }) - }) - - describe('unauthenticated', () => { - it('throws authorization error', async () => { - authenticatedUser = null - await expect(mutate({ mutation: unrewardMutation, variables })).resolves.toMatchObject({ - data: { unreward: null }, - errors: [{ message: 'Not Authorized!' }], - }) - }) - }) - - describe('authenticated admin', () => { - beforeEach(async () => { - authenticatedUser = await administrator.toJson() - }) - - it('removes a badge from user', async () => { - await expect(mutate({ mutation: unrewardMutation, variables })).resolves.toMatchObject( - expected, - ) - }) - - it('does not crash when unrewarding multiple times', async () => { - await mutate({ mutation: unrewardMutation, variables }) - await expect(mutate({ mutation: unrewardMutation, variables })).resolves.toMatchObject( - expected, - ) - }) - }) - - describe('authenticated moderator', () => { - beforeEach(async () => { - authenticatedUser = await moderator.toJson() - }) - - describe('removes bage from user', () => { - it('throws authorization error', async () => { - await expect(mutate({ mutation: unrewardMutation, variables })).resolves.toMatchObject({ - data: { unreward: null }, - errors: [{ message: 'Not Authorized!' }], - }) - }) - }) - }) - }) -}) diff --git a/backend/src/schema/resolvers/rewards.ts b/backend/src/schema/resolvers/rewards.ts deleted file mode 100644 index bbb889c41..000000000 --- a/backend/src/schema/resolvers/rewards.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { UserInputError } from 'apollo-server' - -import { getNeode } from '@db/neo4j' - -const neode = getNeode() - -const getUserAndBadge = async ({ badgeKey, userId }) => { - const user = await neode.first('User', 'id', userId) - const badge = await neode.first('Badge', 'id', badgeKey) - if (!user) throw new UserInputError("Couldn't find a user with that id") - if (!badge) throw new UserInputError("Couldn't find a badge with that id") - return { user, badge } -} - -export default { - Mutation: { - reward: async (_object, params, context, _resolveInfo) => { - const { user, badge } = await getUserAndBadge(params) - await user.relateTo(badge, 'rewarded') - return user.toJson() - }, - - unreward: async (_object, params, context, _resolveInfo) => { - const { badgeKey, userId } = params - const { user } = await getUserAndBadge(params) - const session = context.driver.session() - try { - await session.writeTransaction((transaction) => { - return transaction.run( - ` - MATCH (badge:Badge {id: $badgeKey})-[reward:REWARDED]->(rewardedUser:User {id: $userId}) - DELETE reward - RETURN rewardedUser - `, - { - badgeKey, - userId, - }, - ) - }) - } finally { - session.close() - } - return user.toJson() - }, - }, -} diff --git a/backend/src/schema/resolvers/users.spec.ts b/backend/src/schema/resolvers/users.spec.ts index 0b14575db..486d7e033 100644 --- a/backend/src/schema/resolvers/users.spec.ts +++ b/backend/src/schema/resolvers/users.spec.ts @@ -70,6 +70,36 @@ const updateOnlineStatus = gql` } ` +const setTrophyBadgeSelected = gql` + mutation ($slot: Int!, $badgeId: ID!) { + setTrophyBadgeSelected(slot: $slot, badgeId: $badgeId) { + badgeTrophiesCount + badgeTrophiesSelected { + id + } + badgeTrophiesUnused { + id + } + badgeTrophiesUnusedCount + } + } +` + +const resetTrophyBadgesSelected = gql` + mutation { + resetTrophyBadgesSelected { + badgeTrophiesCount + badgeTrophiesSelected { + id + } + badgeTrophiesUnused { + id + } + badgeTrophiesUnusedCount + } + } +` + beforeAll(async () => { await cleanDatabase() @@ -1070,3 +1100,279 @@ describe('updateOnlineStatus', () => { }) }) }) + +describe('setTrophyBadgeSelected', () => { + beforeEach(async () => { + user = await Factory.build('user', { + id: 'user', + role: 'user', + }) + const badgeBear = await Factory.build('badge', { + id: 'trophy_bear', + type: 'trophy', + description: 'You earned a Bear', + icon: '/img/badges/trophy_blue_bear.svg', + }) + const badgePanda = await Factory.build('badge', { + id: 'trophy_panda', + type: 'trophy', + description: 'You earned a Panda', + icon: '/img/badges/trophy_blue_panda.svg', + }) + await Factory.build('badge', { + id: 'trophy_rabbit', + type: 'trophy', + description: 'You earned a Rabbit', + icon: '/img/badges/trophy_blue_rabbit.svg', + }) + + await user.relateTo(badgeBear, 'rewarded') + await user.relateTo(badgePanda, 'rewarded') + }) + + describe('not authenticated', () => { + beforeEach(async () => { + authenticatedUser = undefined + }) + + it('throws an error', async () => { + await expect( + mutate({ + mutation: setTrophyBadgeSelected, + variables: { slot: 0, badgeId: 'trophy_bear' }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [ + expect.objectContaining({ + message: 'Not Authorized!', + }), + ], + }), + ) + }) + }) + + describe('authenticated', () => { + beforeEach(async () => { + authenticatedUser = await user.toJson() + }) + + it('throws Error when slot is out of bound', async () => { + await expect( + mutate({ + mutation: setTrophyBadgeSelected, + variables: { slot: -1, badgeId: 'trophy_bear' }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [ + expect.objectContaining({ + message: 'Invalid slot! There is only 9 badge-slots to fill', + }), + ], + }), + ) + await expect( + mutate({ + mutation: setTrophyBadgeSelected, + variables: { slot: 9, badgeId: 'trophy_bear' }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [ + expect.objectContaining({ + message: 'Invalid slot! There is only 9 badge-slots to fill', + }), + ], + }), + ) + }) + + it('throws Error when badge was not rewarded to user', async () => { + await expect( + mutate({ + mutation: setTrophyBadgeSelected, + variables: { slot: 0, badgeId: 'trophy_rabbit' }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [ + expect.objectContaining({ + message: 'Error: You cannot set badges not rewarded to you.', + }), + ], + }), + ) + }) + + it('throws Error when badge is unknown', async () => { + await expect( + mutate({ + mutation: setTrophyBadgeSelected, + variables: { slot: 0, badgeId: 'trophy_unknown' }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [ + expect.objectContaining({ + message: 'Error: You cannot set badges not rewarded to you.', + }), + ], + }), + ) + }) + + it('returns the user with badges set on slots', async () => { + await expect( + mutate({ + mutation: setTrophyBadgeSelected, + variables: { slot: 0, badgeId: 'trophy_bear' }, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + setTrophyBadgeSelected: { + badgeTrophiesCount: 2, + badgeTrophiesSelected: [ + { + id: 'trophy_bear', + }, + null, + null, + null, + null, + null, + null, + null, + null, + ], + badgeTrophiesUnused: [ + { + id: 'trophy_panda', + }, + ], + badgeTrophiesUnusedCount: 1, + }, + }, + }), + ) + await expect( + mutate({ + mutation: setTrophyBadgeSelected, + variables: { slot: 5, badgeId: 'trophy_panda' }, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + setTrophyBadgeSelected: { + badgeTrophiesCount: 2, + badgeTrophiesSelected: [ + { + id: 'trophy_bear', + }, + null, + null, + null, + null, + { + id: 'trophy_panda', + }, + null, + null, + null, + ], + badgeTrophiesUnused: [], + badgeTrophiesUnusedCount: 0, + }, + }, + }), + ) + }) + }) +}) + +describe('resetTrophyBadgesSelected', () => { + beforeEach(async () => { + user = await Factory.build('user', { + id: 'user', + role: 'user', + }) + const badgeBear = await Factory.build('badge', { + id: 'trophy_bear', + type: 'trophy', + description: 'You earned a Bear', + icon: '/img/badges/trophy_blue_bear.svg', + }) + const badgePanda = await Factory.build('badge', { + id: 'trophy_panda', + type: 'trophy', + description: 'You earned a Panda', + icon: '/img/badges/trophy_blue_panda.svg', + }) + await Factory.build('badge', { + id: 'trophy_rabbit', + type: 'trophy', + description: 'You earned a Rabbit', + icon: '/img/badges/trophy_blue_rabbit.svg', + }) + + await user.relateTo(badgeBear, 'rewarded') + await user.relateTo(badgePanda, 'rewarded') + + await mutate({ + mutation: setTrophyBadgeSelected, + variables: { slot: 0, badgeId: 'trophy_bear' }, + }) + await mutate({ + mutation: setTrophyBadgeSelected, + variables: { slot: 5, badgeId: 'trophy_panda' }, + }) + }) + + describe('not authenticated', () => { + beforeEach(async () => { + authenticatedUser = undefined + }) + + it('throws an error', async () => { + await expect(mutate({ mutation: resetTrophyBadgesSelected })).resolves.toEqual( + expect.objectContaining({ + errors: [ + expect.objectContaining({ + message: 'Not Authorized!', + }), + ], + }), + ) + }) + }) + + describe('authenticated', () => { + beforeEach(async () => { + authenticatedUser = await user.toJson() + }) + + it('returns the user with no profile badges badges set', async () => { + await expect(mutate({ mutation: resetTrophyBadgesSelected })).resolves.toEqual( + expect.objectContaining({ + data: { + resetTrophyBadgesSelected: { + badgeTrophiesCount: 2, + badgeTrophiesSelected: [null, null, null, null, null, null, null, null, null], + badgeTrophiesUnused: [ + { + id: 'trophy_panda', + }, + { + id: 'trophy_bear', + }, + ], + badgeTrophiesUnusedCount: 2, + }, + }, + }), + ) + }) + }) +}) diff --git a/backend/src/schema/resolvers/users.ts b/backend/src/schema/resolvers/users.ts index e93dffbd0..be9659558 100644 --- a/backend/src/schema/resolvers/users.ts +++ b/backend/src/schema/resolvers/users.ts @@ -1,6 +1,7 @@ import { UserInputError, ForbiddenError } from 'apollo-server' import { neo4jgraphql } from 'neo4j-graphql-js' +import { TROPHY_BADGES_SELECTED_MAX } from '@constants/badges' import { getNeode } from '@db/neo4j' import log from './helpers/databaseLogger' @@ -381,6 +382,73 @@ export default { return true }, + setTrophyBadgeSelected: async (_object, args, context, _resolveInfo) => { + const { slot, badgeId } = args + const { + user: { id: userId }, + } = context + + if (slot >= TROPHY_BADGES_SELECTED_MAX || slot < 0) { + throw new Error( + `Invalid slot! There is only ${TROPHY_BADGES_SELECTED_MAX} badge-slots to fill`, + ) + } + + const session = context.driver.session() + + const query = session.writeTransaction(async (transaction) => { + const result = await transaction.run( + ` + MATCH (user:User {id: $userId})<-[:REWARDED]-(badge:Badge {id: $badgeId}) + OPTIONAL MATCH (user)-[badgeRelation:SELECTED]->(badge) + OPTIONAL MATCH (user)-[slotRelation:SELECTED{slot: $slot}]->(:Badge) + DELETE badgeRelation, slotRelation + MERGE (user)-[:SELECTED{slot: toInteger($slot)}]->(badge) + RETURN user {.*} + `, + { userId, badgeId, slot }, + ) + return result.records.map((record) => record.get('user'))[0] + }) + try { + const user = await query + if (!user) { + throw new Error('You cannot set badges not rewarded to you.') + } + return user + } catch (error) { + throw new Error(error) + } finally { + session.close() + } + }, + resetTrophyBadgesSelected: async (_object, _args, context, _resolveInfo) => { + const { + user: { id: userId }, + } = context + + const session = context.driver.session() + + const query = session.writeTransaction(async (transaction) => { + const result = await transaction.run( + ` + MATCH (user:User {id: $userId}) + OPTIONAL MATCH (user)-[relation:SELECTED]->(:Badge) + DELETE relation + RETURN user {.*} + `, + { userId }, + ) + return result.records.map((record) => record.get('user'))[0] + }) + try { + return await query + } catch (error) { + throw new Error(error) + } finally { + session.close() + } + }, }, User: { emailNotificationSettings: async (parent, params, context, resolveInfo) => { @@ -438,6 +506,87 @@ export default { }, ] }, + badgeTrophiesSelected: async (parent, _params, context, _resolveInfo) => { + const session = context.driver.session() + + const query = session.readTransaction(async (transaction) => { + const result = await transaction.run( + ` + MATCH (user:User {id: $parent.id})-[relation:SELECTED]->(badge:Badge) + WITH relation, badge + ORDER BY relation.slot ASC + RETURN relation.slot as slot, badge {.*} + `, + { parent }, + ) + return result.records + }) + try { + const badgesSelected = await query + const result = Array(TROPHY_BADGES_SELECTED_MAX).fill(null) + badgesSelected.map((record) => { + result[record.get('slot')] = record.get('badge') + return true + }) + return result + } catch (error) { + throw new Error(error) + } finally { + session.close() + } + }, + badgeTrophiesUnused: async (_parent, _params, context, _resolveInfo) => { + const { + user: { id: userId }, + } = context + + const session = context.driver.session() + + const query = session.writeTransaction(async (transaction) => { + const result = await transaction.run( + ` + MATCH (user:User {id: $userId})<-[:REWARDED]-(badge:Badge) + WHERE NOT (user)-[:SELECTED]-(badge) + RETURN badge {.*} + `, + { userId }, + ) + return result.records.map((record) => record.get('badge')) + }) + try { + return await query + } catch (error) { + throw new Error(error) + } finally { + session.close() + } + }, + badgeTrophiesUnusedCount: async (_parent, _params, context, _resolveInfo) => { + const { + user: { id: userId }, + } = context + + const session = context.driver.session() + + const query = session.writeTransaction(async (transaction) => { + const result = await transaction.run( + ` + MATCH (user:User {id: $userId})<-[:REWARDED]-(badge:Badge) + WHERE NOT (user)-[:SELECTED]-(badge) + RETURN toString(COUNT(badge)) as count + `, + { userId }, + ) + return result.records.map((record) => record.get('count'))[0] + }) + try { + return await query + } catch (error) { + throw new Error(error) + } finally { + session.close() + } + }, ...Resolver('User', { undefinedToNull: [ 'actorId', @@ -471,13 +620,14 @@ export default { '-[:WROTE]->(c:Comment)-[:COMMENTS]->(related:Post) WHERE NOT related.disabled = true AND NOT related.deleted = true', shoutedCount: '-[:SHOUTED]->(related:Post) WHERE NOT related.disabled = true AND NOT related.deleted = true', - badgesCount: '<-[:REWARDED]-(related:Badge)', + badgeTrophiesCount: '<-[:REWARDED]-(related:Badge)', }, hasOne: { avatar: '-[:AVATAR_IMAGE]->(related:Image)', invitedBy: '<-[:INVITED]-(related:User)', location: '-[:IS_IN]->(related:Location)', redeemedInviteCode: '-[:REDEEMED]->(related:InviteCode)', + badgeVerification: '<-[:VERIFIES]-(related:Badge)', }, hasMany: { followedBy: '<-[:FOLLOWS]-(related:User)', @@ -488,7 +638,7 @@ export default { comments: '-[:WROTE]->(related:Comment)', shouted: '-[:SHOUTED]->(related:Post)', categories: '-[:CATEGORIZED]->(related:Category)', - badges: '<-[:REWARDED]-(related:Badge)', + badgeTrophies: '<-[:REWARDED]-(related:Badge)', inviteCodes: '-[:GENERATED]->(related:InviteCode)', }, }), diff --git a/backend/src/schema/types/type/Badge.gql b/backend/src/schema/types/type/Badge.gql index dff1de89a..cbfe0193d 100644 --- a/backend/src/schema/types/type/Badge.gql +++ b/backend/src/schema/types/type/Badge.gql @@ -1,22 +1,17 @@ type Badge { id: ID! type: BadgeType! - status: BadgeStatus! icon: String! createdAt: String - updatedAt: String + description: String! rewarded: [User]! @relation(name: "REWARDED", direction: "OUT") -} - -enum BadgeStatus { - permanent - temporary + verifies: [User]! @relation(name: "VERIFIES", direction: "OUT") } enum BadgeType { - role - crowdfunding + verification + trophy } type Query { @@ -24,6 +19,7 @@ type Query { } type Mutation { - reward(badgeKey: ID!, userId: ID!): User - unreward(badgeKey: ID!, userId: ID!): User + setVerificationBadge(badgeId: ID!, userId: ID!): User + rewardTrophyBadge(badgeId: ID!, userId: ID!): User + revokeBadge(badgeId: ID!, userId: ID!): User } diff --git a/backend/src/schema/types/type/User.gql b/backend/src/schema/types/type/User.gql index cac622316..f1a2bcc15 100644 --- a/backend/src/schema/types/type/User.gql +++ b/backend/src/schema/types/type/User.gql @@ -125,8 +125,12 @@ type User { categories: [Category] @relation(name: "CATEGORIZED", direction: "OUT") - badges: [Badge]! @relation(name: "REWARDED", direction: "IN") - badgesCount: Int! @cypher(statement: "MATCH (this)<-[:REWARDED]-(r:Badge) RETURN COUNT(r)") + badgeVerification: Badge @relation(name: "VERIFIES", direction: "IN") + badgeTrophies: [Badge]! @relation(name: "REWARDED", direction: "IN") + badgeTrophiesCount: Int! @cypher(statement: "MATCH (this)<-[:REWARDED]-(r:Badge) RETURN COUNT(r)") + badgeTrophiesSelected: [Badge]! @neo4j_ignore + badgeTrophiesUnused: [Badge]! @neo4j_ignore + badgeTrophiesUnusedCount: Int! @neo4j_ignore emotions: [EMOTED] @@ -247,4 +251,7 @@ type Mutation { # Get a JWT Token for the given Email and password login(email: String!, password: String!): String! + + setTrophyBadgeSelected(slot: Int!, badgeId: ID!): User + resetTrophyBadgesSelected: User } diff --git a/webapp/components/CommentCard/CommentCard.story.js b/webapp/components/CommentCard/CommentCard.story.js index 75078657e..9a5cff436 100644 --- a/webapp/components/CommentCard/CommentCard.story.js +++ b/webapp/components/CommentCard/CommentCard.story.js @@ -32,8 +32,8 @@ const comment = { location: null, badges: [ { - id: 'indiegogo_en_bear', - icon: '/img/badges/indiegogo_en_bear.svg', + id: 'trophy_bear', + icon: '/img/badges/trophy_blue_bear.svg', __typename: 'Badge', }, ], diff --git a/webapp/components/PostTeaser/PostTeaser.story.js b/webapp/components/PostTeaser/PostTeaser.story.js index e77e85585..41db98c91 100644 --- a/webapp/components/PostTeaser/PostTeaser.story.js +++ b/webapp/components/PostTeaser/PostTeaser.story.js @@ -33,8 +33,8 @@ export const post = { badges: [ { id: 'b4', - key: 'indiegogo_en_bear', - icon: '/img/badges/indiegogo_en_bear.svg', + key: 'trophy_bear', + icon: '/img/badges/trophy_blue_bear.svg', __typename: 'Badge', }, ], diff --git a/webapp/components/UserTeaser/UserTeaser.story.js b/webapp/components/UserTeaser/UserTeaser.story.js index aa8be58ff..1295bf2db 100644 --- a/webapp/components/UserTeaser/UserTeaser.story.js +++ b/webapp/components/UserTeaser/UserTeaser.story.js @@ -41,8 +41,8 @@ export const user = { commentedCount: 3, badges: [ { - id: 'indiegogo_en_bear', - icon: '/img/badges/indiegogo_en_bear.svg', + id: 'trophy_bear', + icon: '/img/badges/trophy_blue_bear.svg', }, ], location: { diff --git a/webapp/constants/badges.js b/webapp/constants/badges.js new file mode 100644 index 000000000..bccebb39a --- /dev/null +++ b/webapp/constants/badges.js @@ -0,0 +1,2 @@ +// this file is duplicated in `backend/src/constants/badges` and `webapp/constants/badges.js` +export const TROPHY_BADGES_SELECTED_MAX = 9 diff --git a/webapp/graphql/CommentMutations.js b/webapp/graphql/CommentMutations.js index dd00527be..f413a4496 100644 --- a/webapp/graphql/CommentMutations.js +++ b/webapp/graphql/CommentMutations.js @@ -29,7 +29,7 @@ export default (i18n) => { commentedCount followedByCount followedByCurrentUser - badges { + badgeTrophies { id icon } diff --git a/webapp/graphql/Fragments.js b/webapp/graphql/Fragments.js index 32337230b..d0ad8a0fe 100644 --- a/webapp/graphql/Fragments.js +++ b/webapp/graphql/Fragments.js @@ -26,7 +26,7 @@ export const locationFragment = (lang) => gql` export const badgesFragment = gql` fragment badges on User { - badges { + badgeTrophies { id icon } diff --git a/webapp/pages/profile/_id/_slug.vue b/webapp/pages/profile/_id/_slug.vue index e60ba1098..382350faf 100644 --- a/webapp/pages/profile/_id/_slug.vue +++ b/webapp/pages/profile/_id/_slug.vue @@ -42,8 +42,8 @@ {{ $t('profile.memberSince') }} {{ user.createdAt | date('MMMM yyyy') }} - - + + From 26ac957eb5c32b164e03cfbfdfca42f6dd28e188 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 19 Apr 2025 21:00:24 +0200 Subject: [PATCH 14/54] build(deps-dev): bump @eslint-community/eslint-plugin-eslint-comments (#8415) Bumps [@eslint-community/eslint-plugin-eslint-comments](https://github.com/eslint-community/eslint-plugin-eslint-comments) from 4.4.1 to 4.5.0. - [Release notes](https://github.com/eslint-community/eslint-plugin-eslint-comments/releases) - [Commits](https://github.com/eslint-community/eslint-plugin-eslint-comments/compare/v4.4.1...v4.5.0) --- updated-dependencies: - dependency-name: "@eslint-community/eslint-plugin-eslint-comments" dependency-version: 4.5.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- backend/package.json | 2 +- backend/yarn.lock | 15 +++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/backend/package.json b/backend/package.json index f0a4ef947..90d6b3722 100644 --- a/backend/package.json +++ b/backend/package.json @@ -101,7 +101,7 @@ "xregexp": "^5.1.2" }, "devDependencies": { - "@eslint-community/eslint-plugin-eslint-comments": "^4.4.1", + "@eslint-community/eslint-plugin-eslint-comments": "^4.5.0", "@faker-js/faker": "9.6.0", "@types/jest": "^29.5.14", "@types/node": "^22.14.1", diff --git a/backend/yarn.lock b/backend/yarn.lock index 9e64960ff..9fc5648e3 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -1091,10 +1091,10 @@ dependencies: tslib "^2.4.0" -"@eslint-community/eslint-plugin-eslint-comments@^4.4.1": - version "4.4.1" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-4.4.1.tgz#dbfab6f2447c22be8758a0a9a9c80e56d2e2b93f" - integrity sha512-lb/Z/MzbTf7CaVYM9WCFNQZ4L1yi3ev2fsFPF99h31ljhSEyUoyEsKsNWiU+qD1glbYTDJdqgyaLKtyTkkqtuQ== +"@eslint-community/eslint-plugin-eslint-comments@^4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-4.5.0.tgz#4ffa576583bd99dfbaf74c893635e2c76acba048" + integrity sha512-MAhuTKlr4y/CE3WYX26raZjy+I/kS2PLKSzvfmDCGrBLTFHOYwqROZdr4XwPgXwX3K9rjzMr4pSmUWGnzsUyMg== dependencies: escape-string-regexp "^4.0.0" ignore "^5.2.4" @@ -5942,12 +5942,7 @@ ignore-by-default@^1.0.1: resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= -ignore@^5.1.4, ignore@^5.2.0, ignore@^5.2.4: - version "5.3.0" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78" - integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg== - -ignore@^5.3.2: +ignore@^5.1.4, ignore@^5.2.0, ignore@^5.2.4, ignore@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== From f9368112de55de3ff1c58f1b0b739b02e5a579b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 19 Apr 2025 19:29:39 +0000 Subject: [PATCH 15/54] build(deps): bump nodemailer from 6.10.0 to 6.10.1 in /backend (#8417) Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 6.10.0 to 6.10.1. - [Release notes](https://github.com/nodemailer/nodemailer/releases) - [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodemailer/nodemailer/compare/v6.10.0...v6.10.1) --- updated-dependencies: - dependency-name: nodemailer dependency-version: 6.10.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- backend/package.json | 2 +- backend/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/package.json b/backend/package.json index 90d6b3722..bc757fbb7 100644 --- a/backend/package.json +++ b/backend/package.json @@ -89,7 +89,7 @@ "neo4j-graphql-js": "^2.11.5", "neode": "^0.4.9", "node-fetch": "^2.7.0", - "nodemailer": "^6.10.0", + "nodemailer": "^6.10.1", "nodemailer-html-to-text": "^3.2.0", "request": "~2.88.2", "sanitize-html": "~2.15.0", diff --git a/backend/yarn.lock b/backend/yarn.lock index 9fc5648e3..cd2ce1050 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -7853,10 +7853,10 @@ nodemailer-html-to-text@^3.2.0: dependencies: html-to-text "7.1.1" -nodemailer@^6.10.0: - version "6.10.0" - resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.10.0.tgz#1f24c9de94ad79c6206f66d132776b6503003912" - integrity sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA== +nodemailer@^6.10.1: + version "6.10.1" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.10.1.tgz#cbc434c54238f83a51c07eabd04e2b3e832da623" + integrity sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA== nodemon@~3.1.9: version "3.1.9" From 53f3a4e2e69ec8bd405888daa70c5530746d1bf0 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Mon, 21 Apr 2025 12:01:53 +0200 Subject: [PATCH 16/54] lint @typescript-eslint/recommended (#8406) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Wolfgang Huß --- backend/.eslintrc.cjs | 9 ++++++- backend/src/config/index.ts | 1 + backend/src/db/compiler.ts | 2 +- backend/src/db/factories.ts | 26 +++++++++---------- backend/src/db/migrate/template.ts | 4 +-- ...123150105-merge_duplicate_user_accounts.ts | 5 +++- ...23150110-merge_duplicate_location_nodes.ts | 3 +++ ..._between_existing_blocked_relationships.ts | 2 +- .../migrations/1613589876420-null_mutation.ts | 5 ++-- ...1614023644903-add-clickedCount-to-posts.ts | 4 +-- ...77130817-add-viewedTeaserCount-to-posts.ts | 4 +-- .../20210506150512-add-donations-node.ts | 4 +-- ...otificationEmails-property-to-all-users.ts | 4 +-- ...text_indices_and_unique_keys_for_groups.ts | 4 +-- .../20230320130345-fulltext-search-indexes.ts | 4 +-- .../20230329150329-article-label-for-posts.ts | 4 +-- .../20230608130637-add-postType-property.ts | 4 +-- .../20231017141022-fix-event-dates.ts | 4 +-- ...20250331130323-author-observes-own-post.ts | 4 +-- .../20250331140313-commenter-observes-post.ts | 4 +-- ...50405030454-email-notification-settings.ts | 5 ++-- .../20250414220436-delete-old-badges.ts | 4 +-- backend/src/db/seed.ts | 3 +++ .../middleware/hashtags/extractHashtags.ts | 1 + .../src/middleware/helpers/email/sendMail.ts | 2 ++ .../src/middleware/permissionsMiddleware.ts | 12 ++++----- backend/src/middleware/sentryMiddleware.ts | 3 ++- backend/src/models/index.ts | 2 ++ backend/src/schema/resolvers/comments.ts | 2 +- backend/src/schema/resolvers/embeds.spec.ts | 1 + backend/src/schema/resolvers/embeds.ts | 4 +-- .../src/schema/resolvers/embeds/scraper.ts | 1 + backend/src/schema/resolvers/groups.spec.ts | 1 + .../src/schema/resolvers/helpers/Resolver.ts | 10 ++++--- backend/src/schema/resolvers/images/images.ts | 2 ++ backend/src/schema/resolvers/locations.ts | 2 +- backend/src/schema/resolvers/notifications.ts | 4 +-- .../schema/resolvers/passwordReset.spec.ts | 1 + backend/src/schema/resolvers/posts.ts | 12 ++++----- backend/src/schema/resolvers/roles.ts | 2 +- backend/src/schema/resolvers/rooms.spec.ts | 1 + backend/src/schema/resolvers/rooms.ts | 2 +- backend/src/schema/resolvers/searches.ts | 1 + backend/src/schema/resolvers/socialMedia.ts | 6 ++--- backend/src/schema/resolvers/statistics.ts | 1 + backend/src/schema/resolvers/userData.ts | 2 +- .../src/schema/resolvers/user_management.ts | 4 +-- backend/src/schema/resolvers/users.ts | 18 ++++++------- .../src/schema/resolvers/users/location.ts | 1 + .../schema/resolvers/users/mutedUsers.spec.ts | 1 + backend/src/server.ts | 3 ++- 51 files changed, 126 insertions(+), 89 deletions(-) diff --git a/backend/.eslintrc.cjs b/backend/.eslintrc.cjs index cff4c1de1..37d4dda26 100644 --- a/backend/.eslintrc.cjs +++ b/backend/.eslintrc.cjs @@ -16,6 +16,7 @@ module.exports = { 'plugin:promise/recommended', 'plugin:security/recommended-legacy', 'plugin:@eslint-community/eslint-comments/recommended', + 'prettier', ], settings: { 'import/parsers': { @@ -178,9 +179,10 @@ module.exports = { { files: ['*.ts', '*.tsx'], extends: [ - // 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended', // 'plugin:@typescript-eslint/recommended-requiring-type-checking', // 'plugin:@typescript-eslint/strict', + 'prettier', ], rules: { // allow explicitly defined dangling promises @@ -192,6 +194,11 @@ module.exports = { 'import/unambiguous': 'off', // this is not compatible with typeorm, due to joined tables can be null, but are not defined as nullable '@typescript-eslint/no-unnecessary-condition': 'off', + // respect underscore as acceptable unused variable + '@typescript-eslint/no-unused-vars': [ + 'error', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, + ], }, parserOptions: { tsconfigRootDir: __dirname, diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 328bbbd61..db6b21460 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -19,6 +19,7 @@ if (require.resolve) { } // Use Cypress env or process.env +// eslint-disable-next-line @typescript-eslint/no-explicit-any declare let Cypress: any | undefined const env = typeof Cypress !== 'undefined' ? Cypress.env() : process.env // eslint-disable-line no-undef diff --git a/backend/src/db/compiler.ts b/backend/src/db/compiler.ts index 2d897762f..26bf5a1e2 100644 --- a/backend/src/db/compiler.ts +++ b/backend/src/db/compiler.ts @@ -1,5 +1,5 @@ /* eslint-disable import/no-commonjs */ -// eslint-disable-next-line n/no-unpublished-require +// eslint-disable-next-line n/no-unpublished-require, @typescript-eslint/no-var-requires const tsNode = require('ts-node') // eslint-disable-next-line import/no-unassigned-import, import/no-extraneous-dependencies, n/no-unpublished-require require('tsconfig-paths/register') diff --git a/backend/src/db/factories.ts b/backend/src/db/factories.ts index 136f0fe50..1ce8a3329 100644 --- a/backend/src/db/factories.ts +++ b/backend/src/db/factories.ts @@ -40,14 +40,14 @@ Factory.define('category') .attr('id', uuid) .attr('icon', 'globe') .attr('name', 'Global Peace & Nonviolence') - .after((buildObject, options) => { + .after((buildObject, _options) => { return neode.create('Category', buildObject) }) Factory.define('badge') .attr('type', 'crowdfunding') .attr('status', 'permanent') - .after((buildObject, options) => { + .after((buildObject, _options) => { return neode.create('Badge', buildObject) }) @@ -56,7 +56,7 @@ Factory.define('image') .attr('aspectRatio', 1.3333333333333333) .attr('alt', faker.lorem.sentence) .attr('type', 'image/jpeg') - .after((buildObject, options) => { + .after((buildObject, _options) => { const { url: imageUrl } = buildObject if (imageUrl) buildObject.url = uniqueImageUrl(imageUrl) return neode.create('Image', buildObject) @@ -85,21 +85,21 @@ Factory.define('basicUser') Factory.define('userWithoutEmailAddress') .extend('basicUser') .option('about', faker.lorem.paragraph) - .after(async (buildObject, options) => { + .after(async (buildObject, _options) => { return neode.create('User', buildObject) }) Factory.define('userWithAboutNull') .extend('basicUser') .option('about', null) - .after(async (buildObject, options) => { + .after(async (buildObject, _options) => { return neode.create('User', buildObject) }) Factory.define('userWithAboutEmpty') .extend('basicUser') .option('about', '') - .after(async (buildObject, options) => { + .after(async (buildObject, _options) => { return neode.create('User', buildObject) }) @@ -224,7 +224,7 @@ Factory.define('donations') .attr('showDonations', true) .attr('goal', 15000) .attr('progress', 7000) - .after((buildObject, options) => { + .after((buildObject, _options) => { return neode.create('Donations', buildObject) }) @@ -235,13 +235,13 @@ const emailDefaults = { Factory.define('emailAddress') .attrs(emailDefaults) - .after((buildObject, options) => { + .after((buildObject, _options) => { return neode.create('EmailAddress', buildObject) }) Factory.define('unverifiedEmailAddress') .attr(emailDefaults) - .after((buildObject, options) => { + .after((buildObject, _options) => { return neode.create('UnverifiedEmailAddress', buildObject) }) @@ -281,11 +281,11 @@ Factory.define('location') id: 'country.10743216036480410', type: 'country', }) - .after((buildObject, options) => { + .after((buildObject, _options) => { return neode.create('Location', buildObject) }) -Factory.define('report').after((buildObject, options) => { +Factory.define('report').after((buildObject, _options) => { return neode.create('Report', buildObject) }) @@ -293,7 +293,7 @@ Factory.define('tag') .attrs({ name: '#human-connection', }) - .after((buildObject, options) => { + .after((buildObject, _options) => { return neode.create('Tag', buildObject) }) @@ -301,7 +301,7 @@ Factory.define('socialMedia') .attrs({ url: 'https://mastodon.social/@Gargron', }) - .after((buildObject, options) => { + .after((buildObject, _options) => { return neode.create('SocialMedia', buildObject) }) diff --git a/backend/src/db/migrate/template.ts b/backend/src/db/migrate/template.ts index 569f274bf..6ce151e76 100644 --- a/backend/src/db/migrate/template.ts +++ b/backend/src/db/migrate/template.ts @@ -2,7 +2,7 @@ import { getDriver } from '@db/neo4j' export const description = '' -export async function up(next) { +export async function up(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() @@ -23,7 +23,7 @@ export async function up(next) { } } -export async function down(next) { +export async function down(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() diff --git a/backend/src/db/migrations-examples/20200123150105-merge_duplicate_user_accounts.ts b/backend/src/db/migrations-examples/20200123150105-merge_duplicate_user_accounts.ts index df4cec41e..45bdc4fe0 100644 --- a/backend/src/db/migrations-examples/20200123150105-merge_duplicate_user_accounts.ts +++ b/backend/src/db/migrations-examples/20200123150105-merge_duplicate_user_accounts.ts @@ -21,12 +21,14 @@ export function up(next) { rxSession .beginTransaction() .pipe( + // eslint-disable-next-line @typescript-eslint/no-explicit-any flatMap((txc: any) => concat( txc .run('MATCH (email:EmailAddress) RETURN email {.email}') .records() .pipe( + // eslint-disable-next-line @typescript-eslint/no-explicit-any map((record: any) => { const { email } = record.get('email') const normalizedEmail = normalizeEmail(email) @@ -48,6 +50,7 @@ export function up(next) { ) .records() .pipe( + // eslint-disable-next-line @typescript-eslint/no-explicit-any map((r: any) => ({ oldEmail: email, email: r.get('email'), @@ -61,7 +64,7 @@ export function up(next) { ), ) .subscribe({ - next: ({ user, email, oldUser, oldEmail }) => + next: ({ user, email, _oldUser, oldEmail }) => // eslint-disable-next-line no-console console.log(` Merged: diff --git a/backend/src/db/migrations-examples/20200123150110-merge_duplicate_location_nodes.ts b/backend/src/db/migrations-examples/20200123150110-merge_duplicate_location_nodes.ts index 89cef62fc..6aee51c83 100644 --- a/backend/src/db/migrations-examples/20200123150110-merge_duplicate_location_nodes.ts +++ b/backend/src/db/migrations-examples/20200123150110-merge_duplicate_location_nodes.ts @@ -15,6 +15,7 @@ export function up(next) { rxSession .beginTransaction() .pipe( + // eslint-disable-next-line @typescript-eslint/no-explicit-any flatMap((transaction: any) => concat( transaction @@ -26,6 +27,7 @@ export function up(next) { ) .records() .pipe( + // eslint-disable-next-line @typescript-eslint/no-explicit-any map((record: any) => { const { id: locationId } = record.get('location') return { locationId } @@ -43,6 +45,7 @@ export function up(next) { ) .records() .pipe( + // eslint-disable-next-line @typescript-eslint/no-explicit-any map((record: any) => ({ location: record.get('location'), updatedLocation: record.get('updatedLocation'), diff --git a/backend/src/db/migrations-examples/20200127110135-create_muted_relationship_between_existing_blocked_relationships.ts b/backend/src/db/migrations-examples/20200127110135-create_muted_relationship_between_existing_blocked_relationships.ts index 4743ff175..f24bbdee8 100644 --- a/backend/src/db/migrations-examples/20200127110135-create_muted_relationship_between_existing_blocked_relationships.ts +++ b/backend/src/db/migrations-examples/20200127110135-create_muted_relationship_between_existing_blocked_relationships.ts @@ -8,7 +8,7 @@ export const description = ` A blocked user will still be able to see your contributions, but will not be able to interact with them and vice versa. ` -export async function up(next) { +export async function up(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() diff --git a/backend/src/db/migrations/1613589876420-null_mutation.ts b/backend/src/db/migrations/1613589876420-null_mutation.ts index ddd5bb14b..daeba5dca 100644 --- a/backend/src/db/migrations/1613589876420-null_mutation.ts +++ b/backend/src/db/migrations/1613589876420-null_mutation.ts @@ -1,5 +1,6 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ 'use strict' -export async function up(next) {} +export async function up(_next) {} -export async function down(next) {} +export async function down(_next) {} diff --git a/backend/src/db/migrations/1614023644903-add-clickedCount-to-posts.ts b/backend/src/db/migrations/1614023644903-add-clickedCount-to-posts.ts index 25b0addaf..65fb33a68 100644 --- a/backend/src/db/migrations/1614023644903-add-clickedCount-to-posts.ts +++ b/backend/src/db/migrations/1614023644903-add-clickedCount-to-posts.ts @@ -4,7 +4,7 @@ export const description = ` This migration adds the clickedCount property to all posts, setting it to 0. ` -export async function up(next) { +export async function up(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() @@ -27,7 +27,7 @@ export async function up(next) { } } -export async function down(next) { +export async function down(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() diff --git a/backend/src/db/migrations/1614177130817-add-viewedTeaserCount-to-posts.ts b/backend/src/db/migrations/1614177130817-add-viewedTeaserCount-to-posts.ts index 8073d8e22..f4fd7cda4 100644 --- a/backend/src/db/migrations/1614177130817-add-viewedTeaserCount-to-posts.ts +++ b/backend/src/db/migrations/1614177130817-add-viewedTeaserCount-to-posts.ts @@ -4,7 +4,7 @@ export const description = ` This migration adds the viewedTeaserCount property to all posts, setting it to 0. ` -export async function up(next) { +export async function up(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() @@ -27,7 +27,7 @@ export async function up(next) { } } -export async function down(next) { +export async function down(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() diff --git a/backend/src/db/migrations/20210506150512-add-donations-node.ts b/backend/src/db/migrations/20210506150512-add-donations-node.ts index 073d4f348..fcb4d816c 100644 --- a/backend/src/db/migrations/20210506150512-add-donations-node.ts +++ b/backend/src/db/migrations/20210506150512-add-donations-node.ts @@ -5,7 +5,7 @@ import { getDriver } from '@db/neo4j' export const description = 'This migration adds a Donations node with default settings to the database.' -export async function up(next) { +export async function up(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() @@ -39,7 +39,7 @@ export async function up(next) { } } -export async function down(next) { +export async function down(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() diff --git a/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.ts b/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.ts index be0eb8dd6..804d12097 100644 --- a/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.ts +++ b/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.ts @@ -2,7 +2,7 @@ import { getDriver } from '@db/neo4j' export const description = '' -export async function up(next) { +export async function up(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() @@ -29,7 +29,7 @@ export async function up(next) { } } -export async function down(next) { +export async function down(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() diff --git a/backend/src/db/migrations/20220803060819-create_fulltext_indices_and_unique_keys_for_groups.ts b/backend/src/db/migrations/20220803060819-create_fulltext_indices_and_unique_keys_for_groups.ts index a3f220429..815c695e9 100644 --- a/backend/src/db/migrations/20220803060819-create_fulltext_indices_and_unique_keys_for_groups.ts +++ b/backend/src/db/migrations/20220803060819-create_fulltext_indices_and_unique_keys_for_groups.ts @@ -5,7 +5,7 @@ export const description = ` Additional we like to have fulltext indices the keys 'name', 'slug', 'about', and 'description'. ` -export async function up(next) { +export async function up(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() @@ -38,7 +38,7 @@ export async function up(next) { } } -export async function down(next) { +export async function down(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() diff --git a/backend/src/db/migrations/20230320130345-fulltext-search-indexes.ts b/backend/src/db/migrations/20230320130345-fulltext-search-indexes.ts index af5cf2d8c..706e952c0 100644 --- a/backend/src/db/migrations/20230320130345-fulltext-search-indexes.ts +++ b/backend/src/db/migrations/20230320130345-fulltext-search-indexes.ts @@ -2,7 +2,7 @@ import { getDriver } from '@db/neo4j' export const description = '' -export async function up(next) { +export async function up(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() @@ -46,7 +46,7 @@ export async function up(next) { } } -export async function down(next) { +export async function down(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() diff --git a/backend/src/db/migrations/20230329150329-article-label-for-posts.ts b/backend/src/db/migrations/20230329150329-article-label-for-posts.ts index 9f211e60e..d11935833 100644 --- a/backend/src/db/migrations/20230329150329-article-label-for-posts.ts +++ b/backend/src/db/migrations/20230329150329-article-label-for-posts.ts @@ -2,7 +2,7 @@ import { getDriver } from '@db/neo4j' export const description = 'Add to all existing posts the Article label' -export async function up(next) { +export async function up(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() @@ -26,7 +26,7 @@ export async function up(next) { } } -export async function down(next) { +export async function down(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() diff --git a/backend/src/db/migrations/20230608130637-add-postType-property.ts b/backend/src/db/migrations/20230608130637-add-postType-property.ts index 02101b620..c9941f713 100644 --- a/backend/src/db/migrations/20230608130637-add-postType-property.ts +++ b/backend/src/db/migrations/20230608130637-add-postType-property.ts @@ -2,7 +2,7 @@ import { getDriver } from '@db/neo4j' export const description = 'Add postType property Article to all posts' -export async function up(next) { +export async function up(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() @@ -26,7 +26,7 @@ export async function up(next) { } } -export async function down(next) { +export async function down(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() diff --git a/backend/src/db/migrations/20231017141022-fix-event-dates.ts b/backend/src/db/migrations/20231017141022-fix-event-dates.ts index 1e80dfff4..4e9764f63 100644 --- a/backend/src/db/migrations/20231017141022-fix-event-dates.ts +++ b/backend/src/db/migrations/20231017141022-fix-event-dates.ts @@ -5,7 +5,7 @@ Transform event start and end date of format 'YYYY-MM-DD HH:MM:SS' in CEST to ISOString in UTC. ` -export async function up(next) { +export async function up(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() @@ -46,7 +46,7 @@ export async function up(next) { } } -export async function down(next) { +export async function down(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() diff --git a/backend/src/db/migrations/20250331130323-author-observes-own-post.ts b/backend/src/db/migrations/20250331130323-author-observes-own-post.ts index a32539f40..700f69a64 100644 --- a/backend/src/db/migrations/20250331130323-author-observes-own-post.ts +++ b/backend/src/db/migrations/20250331130323-author-observes-own-post.ts @@ -4,7 +4,7 @@ export const description = ` All authors observe their posts. ` -export async function up(next) { +export async function up(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() @@ -33,7 +33,7 @@ export async function up(next) { } } -export async function down(next) { +export async function down(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() diff --git a/backend/src/db/migrations/20250331140313-commenter-observes-post.ts b/backend/src/db/migrations/20250331140313-commenter-observes-post.ts index be55a6b64..6cd97f4f2 100644 --- a/backend/src/db/migrations/20250331140313-commenter-observes-post.ts +++ b/backend/src/db/migrations/20250331140313-commenter-observes-post.ts @@ -4,7 +4,7 @@ export const description = ` All users commenting a post observe the post. ` -export async function up(next) { +export async function up(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() @@ -33,7 +33,7 @@ export async function up(next) { } } -export async function down(next) { +export async function down(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() diff --git a/backend/src/db/migrations/20250405030454-email-notification-settings.ts b/backend/src/db/migrations/20250405030454-email-notification-settings.ts index abe9c658d..2030aea0f 100644 --- a/backend/src/db/migrations/20250405030454-email-notification-settings.ts +++ b/backend/src/db/migrations/20250405030454-email-notification-settings.ts @@ -3,7 +3,7 @@ import { getDriver } from '@db/neo4j' export const description = 'Transforms the `sendNotificationEmails` property on User to a multi value system' -export async function up(next) { +export async function up(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() @@ -34,7 +34,7 @@ export async function up(next) { } } -export async function down(next) { +export async function down(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() @@ -53,7 +53,6 @@ export async function down(next) { REMOVE user.emailNotificationsGroupMemberRoleChanged `) await transaction.commit() - next() } catch (error) { // eslint-disable-next-line no-console console.log(error) diff --git a/backend/src/db/migrations/20250414220436-delete-old-badges.ts b/backend/src/db/migrations/20250414220436-delete-old-badges.ts index db3a53d33..ce37590e8 100644 --- a/backend/src/db/migrations/20250414220436-delete-old-badges.ts +++ b/backend/src/db/migrations/20250414220436-delete-old-badges.ts @@ -2,7 +2,7 @@ import { getDriver } from '@db/neo4j' export const description = '' -export async function up(next) { +export async function up(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() @@ -26,7 +26,7 @@ export async function up(next) { } } -export async function down(next) { +export async function down(_next) { const driver = getDriver() const session = driver.session() const transaction = session.beginTransaction() diff --git a/backend/src/db/seed.ts b/backend/src/db/seed.ts index 5f918235e..a1dc25b11 100644 --- a/backend/src/db/seed.ts +++ b/backend/src/db/seed.ts @@ -911,6 +911,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] authenticatedUser = null + // eslint-disable-next-line @typescript-eslint/no-explicit-any const comments: any[] = [] comments.push( await Factory.build( @@ -1085,6 +1086,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] await huey.relateTo(p9, 'shouted') await louie.relateTo(p10, 'shouted') + // eslint-disable-next-line @typescript-eslint/no-explicit-any const reports: any[] = [] reports.push( await Factory.build('report'), @@ -1192,6 +1194,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] closed: true, }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any const additionalUsers: any[] = [] for (let i = 0; i < 30; i++) { const user = await Factory.build('user') diff --git a/backend/src/middleware/hashtags/extractHashtags.ts b/backend/src/middleware/hashtags/extractHashtags.ts index fc7a93d17..908bb3080 100644 --- a/backend/src/middleware/hashtags/extractHashtags.ts +++ b/backend/src/middleware/hashtags/extractHashtags.ts @@ -19,6 +19,7 @@ export default function (content?) { return $(el).attr('data-hashtag-id') }) .get() + // eslint-disable-next-line @typescript-eslint/no-explicit-any const hashtags: any = [] ids.forEach((id) => { const match = exec(id, regX) diff --git a/backend/src/middleware/helpers/email/sendMail.ts b/backend/src/middleware/helpers/email/sendMail.ts index 46d808742..b8c5cfb00 100644 --- a/backend/src/middleware/helpers/email/sendMail.ts +++ b/backend/src/middleware/helpers/email/sendMail.ts @@ -28,6 +28,7 @@ const transporter = nodemailer.createTransport({ }, }) +// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-function let sendMailCallback: any = async () => {} if (!hasEmailConfig) { if (!CONFIG.TEST) { @@ -51,6 +52,7 @@ if (!hasEmailConfig) { cleanHtml(templateArgs.html, 'dummyKey', { allowedTags: ['a'], allowedAttributes: { a: ['href'] }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any).replace(/&/g, '&'), ) } diff --git a/backend/src/middleware/permissionsMiddleware.ts b/backend/src/middleware/permissionsMiddleware.ts index aa48d9d10..5ba9c8e4c 100644 --- a/backend/src/middleware/permissionsMiddleware.ts +++ b/backend/src/middleware/permissionsMiddleware.ts @@ -15,23 +15,23 @@ const isAuthenticated = rule({ return !!(ctx && ctx.user && ctx.user.id) }) -const isModerator = rule()(async (parent, args, { user }, info) => { +const isModerator = rule()(async (_parent, _args, { user }, _info) => { return user && (user.role === 'moderator' || user.role === 'admin') }) -const isAdmin = rule()(async (parent, args, { user }, info) => { +const isAdmin = rule()(async (_parent, _args, { user }, _info) => { return user && user.role === 'admin' }) const onlyYourself = rule({ cache: 'no_cache', -})(async (parent, args, context, info) => { +})(async (_parent, args, context, _info) => { return context.user.id === args.id }) const isMyOwn = rule({ cache: 'no_cache', -})(async (parent, args, { user }, info) => { +})(async (parent, _args, { user }, _info) => { return user && user.id === parent.id }) @@ -350,7 +350,7 @@ const isAuthor = rule({ const isDeletingOwnAccount = rule({ cache: 'no_cache', -})(async (parent, args, context, _info) => { +})(async (_parent, args, context, _info) => { return context.user.id === args.id }) @@ -362,7 +362,7 @@ const noEmailFilter = rule({ const publicRegistration = rule()(() => CONFIG.PUBLIC_REGISTRATION) -const inviteRegistration = rule()(async (_parent, args, { user, driver }) => { +const inviteRegistration = rule()(async (_parent, args, { _user, driver }) => { if (!CONFIG.INVITE_REGISTRATION) return false const { inviteCode } = args const session = driver.session() diff --git a/backend/src/middleware/sentryMiddleware.ts b/backend/src/middleware/sentryMiddleware.ts index b77f680d6..dab0c8353 100644 --- a/backend/src/middleware/sentryMiddleware.ts +++ b/backend/src/middleware/sentryMiddleware.ts @@ -2,7 +2,7 @@ import { sentry } from 'graphql-middleware-sentry' import CONFIG from '@config/index' -// eslint-disable-next-line import/no-mutable-exports +// eslint-disable-next-line import/no-mutable-exports, @typescript-eslint/no-explicit-any let sentryMiddleware: any = (resolve, root, args, context, resolveInfo) => resolve(root, args, context, resolveInfo) @@ -14,6 +14,7 @@ if (CONFIG.SENTRY_DSN_BACKEND) { release: CONFIG.COMMIT, environment: CONFIG.NODE_ENV, }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any withScope: (scope, error, context: any) => { scope.setUser({ id: context.user && context.user.id, diff --git a/backend/src/models/index.ts b/backend/src/models/index.ts index e02cbc242..74917e77e 100644 --- a/backend/src/models/index.ts +++ b/backend/src/models/index.ts @@ -1,7 +1,9 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable n/no-missing-require */ /* eslint-disable n/global-require */ // NOTE: We cannot use `fs` here to clean up the code. Cypress breaks on any npm // module that is not browser-compatible. Node's `fs` module is server-side only +// eslint-disable-next-line @typescript-eslint/no-explicit-any declare let Cypress: any | undefined export default { Image: typeof Cypress !== 'undefined' ? require('./Image') : require('./Image').default, diff --git a/backend/src/schema/resolvers/comments.ts b/backend/src/schema/resolvers/comments.ts index 897c71d6f..4072f470c 100644 --- a/backend/src/schema/resolvers/comments.ts +++ b/backend/src/schema/resolvers/comments.ts @@ -4,7 +4,7 @@ import Resolver from './helpers/Resolver' export default { Mutation: { - CreateComment: async (object, params, context, resolveInfo) => { + CreateComment: async (_object, params, context, _resolveInfo) => { const { postId } = params const { user, driver } = context // Adding relationship from comment to post by passing in the postId, diff --git a/backend/src/schema/resolvers/embeds.spec.ts b/backend/src/schema/resolvers/embeds.spec.ts index 92dd224e3..7a49986cf 100644 --- a/backend/src/schema/resolvers/embeds.spec.ts +++ b/backend/src/schema/resolvers/embeds.spec.ts @@ -55,6 +55,7 @@ describe('Query', () => { beforeEach(() => { embedAction = async (variables) => { const { server } = createServer({ + // eslint-disable-next-line @typescript-eslint/no-empty-function context: () => {}, }) const { query } = createTestClient(server) diff --git a/backend/src/schema/resolvers/embeds.ts b/backend/src/schema/resolvers/embeds.ts index a75365ec7..a4368a42f 100644 --- a/backend/src/schema/resolvers/embeds.ts +++ b/backend/src/schema/resolvers/embeds.ts @@ -3,7 +3,7 @@ import { undefinedToNullResolver } from './helpers/Resolver' export default { Query: { - embed: async (object, { url }, context, resolveInfo) => { + embed: async (_object, { url }, _context, _resolveInfo) => { return scrape(url) }, }, @@ -22,7 +22,7 @@ export default { 'lang', 'html', ]), - sources: async (parent, params, context, resolveInfo) => { + sources: async (parent, _params, _context, _resolveInfo) => { return typeof parent.sources === 'undefined' ? [] : parent.sources }, }, diff --git a/backend/src/schema/resolvers/embeds/scraper.ts b/backend/src/schema/resolvers/embeds/scraper.ts index e4e19e6b9..1942917bf 100644 --- a/backend/src/schema/resolvers/embeds/scraper.ts +++ b/backend/src/schema/resolvers/embeds/scraper.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable n/no-extraneous-require */ /* eslint-disable n/global-require */ /* eslint-disable import/no-commonjs */ diff --git a/backend/src/schema/resolvers/groups.spec.ts b/backend/src/schema/resolvers/groups.spec.ts index 624b09e39..d9d060fea 100644 --- a/backend/src/schema/resolvers/groups.spec.ts +++ b/backend/src/schema/resolvers/groups.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { createTestClient } from 'apollo-server-testing' import CONFIG from '@config/index' diff --git a/backend/src/schema/resolvers/helpers/Resolver.ts b/backend/src/schema/resolvers/helpers/Resolver.ts index a21893f7d..8119614df 100644 --- a/backend/src/schema/resolvers/helpers/Resolver.ts +++ b/backend/src/schema/resolvers/helpers/Resolver.ts @@ -11,6 +11,7 @@ export const undefinedToNullResolver = (list) => { return resolvers } +// eslint-disable-next-line @typescript-eslint/no-explicit-any export default function Resolver(type, options: any = {}) { const { idAttribute = 'id', @@ -21,8 +22,8 @@ export default function Resolver(type, options: any = {}) { hasMany = {}, } = options - const _hasResolver = (resolvers, { key, connection }, { returnType }) => { - return async (parent, params, { driver, cypherParams }, resolveInfo) => { + const _hasResolver = (_resolvers, { key, connection }, { returnType }) => { + return async (parent, _params, { driver, cypherParams }, _resolveInfo) => { if (typeof parent[key] !== 'undefined') return parent[key] const id = parent[idAttribute] const session = driver.session() @@ -45,10 +46,11 @@ export default function Resolver(type, options: any = {}) { } } + // eslint-disable-next-line @typescript-eslint/no-explicit-any const booleanResolver = (obj: any[]) => { const resolvers = {} for (const [key, condition] of Object.entries(obj)) { - resolvers[key] = async (parent, params, { cypherParams, driver }, resolveInfo) => { + resolvers[key] = async (parent, _params, { cypherParams, driver }, _resolveInfo) => { if (typeof parent[key] !== 'undefined') return parent[key] const id = parent[idAttribute] const session = driver.session() @@ -73,7 +75,7 @@ export default function Resolver(type, options: any = {}) { const countResolver = (obj) => { const resolvers = {} for (const [key, connection] of Object.entries(obj)) { - resolvers[key] = async (parent, params, { driver, cypherParams }, resolveInfo) => { + resolvers[key] = async (parent, _params, { driver, cypherParams }, _resolveInfo) => { if (typeof parent[key] !== 'undefined') return parent[key] const session = driver.session() const readTxResultPromise = session.readTransaction(async (txc) => { diff --git a/backend/src/schema/resolvers/images/images.ts b/backend/src/schema/resolvers/images/images.ts index 46eb453c5..b7d6f3110 100644 --- a/backend/src/schema/resolvers/images/images.ts +++ b/backend/src/schema/resolvers/images/images.ts @@ -14,6 +14,7 @@ import { getDriver } from '@db/neo4j' // const widths = [34, 160, 320, 640, 1024] const { AWS_ENDPOINT: endpoint, AWS_REGION: region, AWS_BUCKET: Bucket, S3_CONFIGURED } = CONFIG +// eslint-disable-next-line @typescript-eslint/no-explicit-any export async function deleteImage(resource, relationshipType, opts: any = {}) { sanitizeRelationshipType(relationshipType) const { transaction, deleteCallback } = opts @@ -36,6 +37,7 @@ export async function deleteImage(resource, relationshipType, opts: any = {}) { return image } +// eslint-disable-next-line @typescript-eslint/no-explicit-any export async function mergeImage(resource, relationshipType, imageInput, opts: any = {}) { if (typeof imageInput === 'undefined') return if (imageInput === null) return deleteImage(resource, relationshipType, opts) diff --git a/backend/src/schema/resolvers/locations.ts b/backend/src/schema/resolvers/locations.ts index fcc2fa0aa..5c2f67e20 100644 --- a/backend/src/schema/resolvers/locations.ts +++ b/backend/src/schema/resolvers/locations.ts @@ -20,7 +20,7 @@ export default { }), }, Query: { - queryLocations: async (object, args, context, resolveInfo) => { + queryLocations: async (_object, args, _context, _resolveInfo) => { try { return queryLocations(args) } catch (e) { diff --git a/backend/src/schema/resolvers/notifications.ts b/backend/src/schema/resolvers/notifications.ts index 5dbbe3d40..c93655ae3 100644 --- a/backend/src/schema/resolvers/notifications.ts +++ b/backend/src/schema/resolvers/notifications.ts @@ -82,7 +82,7 @@ export default { }, }, Mutation: { - markAsRead: async (parent, args, context, resolveInfo) => { + markAsRead: async (_parent, args, context, _resolveInfo) => { const { user: currentUser } = context const session = context.driver.session() const writeTxResultPromise = session.writeTransaction(async (transaction) => { @@ -112,7 +112,7 @@ export default { session.close() } }, - markAllAsRead: async (parent, args, context, resolveInfo) => { + markAllAsRead: async (parent, args, context, _resolveInfo) => { const { user: currentUser } = context const session = context.driver.session() const writeTxResultPromise = session.writeTransaction(async (transaction) => { diff --git a/backend/src/schema/resolvers/passwordReset.spec.ts b/backend/src/schema/resolvers/passwordReset.spec.ts index b5c7e10dd..e88a1e503 100644 --- a/backend/src/schema/resolvers/passwordReset.spec.ts +++ b/backend/src/schema/resolvers/passwordReset.spec.ts @@ -121,6 +121,7 @@ describe('passwordReset', () => { }) describe('resetPassword', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const setup = async (options: any = {}) => { const { email = 'user@example.org', issuedAt = new Date(), nonce = '12345' } = options await createPasswordReset({ driver, email, issuedAt, nonce }) diff --git a/backend/src/schema/resolvers/posts.ts b/backend/src/schema/resolvers/posts.ts index cb48d78ea..c7d3a1b14 100644 --- a/backend/src/schema/resolvers/posts.ts +++ b/backend/src/schema/resolvers/posts.ts @@ -48,7 +48,7 @@ export default { params = await filterForMutedUsers(params, context) return neo4jgraphql(object, params, context, resolveInfo) }, - PostsEmotionsCountByEmotion: async (object, params, context, resolveInfo) => { + PostsEmotionsCountByEmotion: async (_object, params, context, _resolveInfo) => { const { postId, data } = params const session = context.driver.session() const readTxResultPromise = session.readTransaction(async (transaction) => { @@ -70,7 +70,7 @@ export default { session.close() } }, - PostsEmotionsByCurrentUser: async (object, params, context, resolveInfo) => { + PostsEmotionsByCurrentUser: async (_object, params, context, _resolveInfo) => { const { postId } = params const session = context.driver.session() const readTxResultPromise = session.readTransaction(async (transaction) => { @@ -242,7 +242,7 @@ export default { } }, - DeletePost: async (object, args, context, resolveInfo) => { + DeletePost: async (_object, args, context, _resolveInfo) => { const session = context.driver.session() const writeTxResultPromise = session.writeTransaction(async (transaction) => { const deletePostTransactionResponse = await transaction.run( @@ -269,7 +269,7 @@ export default { session.close() } }, - AddPostEmotions: async (object, params, context, resolveInfo) => { + AddPostEmotions: async (_object, params, context, _resolveInfo) => { const { to, data } = params const { user } = context const session = context.driver.session() @@ -296,7 +296,7 @@ export default { session.close() } }, - RemovePostEmotions: async (object, params, context, resolveInfo) => { + RemovePostEmotions: async (_object, params, context, _resolveInfo) => { const { to, data } = params const { id: from } = context.user const session = context.driver.session() @@ -499,7 +499,7 @@ export default { 'MATCH (this)<-[obs:OBSERVES]-(related:User {id: $cypherParams.currentUserId}) WHERE obs.active = true RETURN COUNT(related) >= 1', }, }), - relatedContributions: async (parent, params, context, resolveInfo) => { + relatedContributions: async (parent, _params, context, _resolveInfo) => { if (typeof parent.relatedContributions !== 'undefined') return parent.relatedContributions const { id } = parent const session = context.driver.session() diff --git a/backend/src/schema/resolvers/roles.ts b/backend/src/schema/resolvers/roles.ts index be9861e08..fba57c35a 100644 --- a/backend/src/schema/resolvers/roles.ts +++ b/backend/src/schema/resolvers/roles.ts @@ -1,6 +1,6 @@ export default { Query: { - availableRoles: async (_parent, args, context, _resolveInfo) => { + availableRoles: async (_parent, _args, _context, _resolveInfo) => { return ['admin', 'moderator', 'user'] }, }, diff --git a/backend/src/schema/resolvers/rooms.spec.ts b/backend/src/schema/resolvers/rooms.spec.ts index 2025681ad..25fb72214 100644 --- a/backend/src/schema/resolvers/rooms.spec.ts +++ b/backend/src/schema/resolvers/rooms.spec.ts @@ -586,6 +586,7 @@ describe('Room', () => { }) describe('query single room', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any let result: any = null beforeAll(async () => { diff --git a/backend/src/schema/resolvers/rooms.ts b/backend/src/schema/resolvers/rooms.ts index 1bfa354e9..7b87e9007 100644 --- a/backend/src/schema/resolvers/rooms.ts +++ b/backend/src/schema/resolvers/rooms.ts @@ -39,7 +39,7 @@ export default { } return neo4jgraphql(object, params, context, resolveInfo) }, - UnreadRooms: async (object, params, context, resolveInfo) => { + UnreadRooms: async (_object, _params, context, _resolveInfo) => { const { user: { id: currentUserId }, } = context diff --git a/backend/src/schema/resolvers/searches.ts b/backend/src/schema/resolvers/searches.ts index 5f4097c17..711166d5e 100644 --- a/backend/src/schema/resolvers/searches.ts +++ b/backend/src/schema/resolvers/searches.ts @@ -250,6 +250,7 @@ export default { ] params.limit = 15 + // eslint-disable-next-line @typescript-eslint/no-explicit-any const type: any = multiSearchMap.find((obj) => obj.symbol === searchType) return getSearchResults(context, type.setup, params) }, diff --git a/backend/src/schema/resolvers/socialMedia.ts b/backend/src/schema/resolvers/socialMedia.ts index ac27eb1f9..53877e431 100644 --- a/backend/src/schema/resolvers/socialMedia.ts +++ b/backend/src/schema/resolvers/socialMedia.ts @@ -6,7 +6,7 @@ const neode = getNeode() export default { Mutation: { - CreateSocialMedia: async (object, params, context, resolveInfo) => { + CreateSocialMedia: async (_object, params, context, _resolveInfo) => { const [user, socialMedia] = await Promise.all([ neode.find('User', context.user.id), neode.create('SocialMedia', params), @@ -16,14 +16,14 @@ export default { return response }, - UpdateSocialMedia: async (object, params, context, resolveInfo) => { + UpdateSocialMedia: async (_object, params, _context, _resolveInfo) => { const socialMedia = await neode.find('SocialMedia', params.id) await socialMedia.update({ url: params.url }) const response = await socialMedia.toJson() return response }, - DeleteSocialMedia: async (object, { id }, context, resolveInfo) => { + DeleteSocialMedia: async (_object, { id }, _context, _resolveInfo) => { const socialMedia = await neode.find('SocialMedia', id) if (!socialMedia) return null await socialMedia.delete() diff --git a/backend/src/schema/resolvers/statistics.ts b/backend/src/schema/resolvers/statistics.ts index 6bf73b0b2..6bdd7ac5e 100644 --- a/backend/src/schema/resolvers/statistics.ts +++ b/backend/src/schema/resolvers/statistics.ts @@ -5,6 +5,7 @@ export default { Query: { statistics: async (_parent, _args, { driver }) => { const session = driver.session() + // eslint-disable-next-line @typescript-eslint/no-explicit-any const counts: any = {} try { const mapping = { diff --git a/backend/src/schema/resolvers/userData.ts b/backend/src/schema/resolvers/userData.ts index 3cd5f1c01..5c892b7ab 100644 --- a/backend/src/schema/resolvers/userData.ts +++ b/backend/src/schema/resolvers/userData.ts @@ -1,6 +1,6 @@ export default { Query: { - userData: async (object, args, context, resolveInfo) => { + userData: async (_object, _args, context, _resolveInfo) => { const id = context.user.id const cypher = ` MATCH (user:User { id: $id }) diff --git a/backend/src/schema/resolvers/user_management.ts b/backend/src/schema/resolvers/user_management.ts index e9376f940..70b4f6520 100644 --- a/backend/src/schema/resolvers/user_management.ts +++ b/backend/src/schema/resolvers/user_management.ts @@ -16,7 +16,7 @@ export default { neo4jgraphql(object, { id: context.user.id }, context, resolveInfo), }, Mutation: { - login: async (_, { email, password }, { driver, req, user }) => { + login: async (_, { email, password }, { driver }) => { // if (user && user.id) { // throw new Error('Already logged in.') // } @@ -51,7 +51,7 @@ export default { session.close() } }, - changePassword: async (_, { oldPassword, newPassword }, { driver, user }) => { + changePassword: async (_, { oldPassword, newPassword }, { user }) => { const currentUser = await neode.find('User', user.id) const encryptedPassword = currentUser.get('encryptedPassword') diff --git a/backend/src/schema/resolvers/users.ts b/backend/src/schema/resolvers/users.ts index be9659558..578726dc6 100644 --- a/backend/src/schema/resolvers/users.ts +++ b/backend/src/schema/resolvers/users.ts @@ -43,14 +43,14 @@ export const getBlockedUsers = async (context) => { export default { Query: { - mutedUsers: async (object, args, context, resolveInfo) => { + mutedUsers: async (_object, _args, context, _resolveInfo) => { try { return getMutedUsers(context) } catch (e) { throw new UserInputError(e.message) } }, - blockedUsers: async (object, args, context, resolveInfo) => { + blockedUsers: async (_object, _args, context, _resolveInfo) => { try { return getBlockedUsers(context) } catch (e) { @@ -111,7 +111,7 @@ export default { const unmutedUser = await neode.find('User', params.id) return unmutedUser.toJson() }, - blockUser: async (object, args, context, resolveInfo) => { + blockUser: async (_object, args, context, _resolveInfo) => { const { user: currentUser } = context if (currentUser.id === args.id) return null @@ -138,7 +138,7 @@ export default { session.close() } }, - unblockUser: async (object, args, context, resolveInfo) => { + unblockUser: async (_object, args, context, _resolveInfo) => { const { user: currentUser } = context if (currentUser.id === args.id) return null @@ -216,7 +216,7 @@ export default { session.close() } }, - DeleteUser: async (object, params, context, resolveInfo) => { + DeleteUser: async (_object, params, context, _resolveInfo) => { const { resource, id: userId } = params const session = context.driver.session() @@ -283,7 +283,7 @@ export default { session.close() } }, - switchUserRole: async (object, args, context, resolveInfo) => { + switchUserRole: async (_object, args, context, _resolveInfo) => { const { role, id } = args if (context.user.id === id) throw new Error('you-cannot-change-your-own-role') @@ -308,7 +308,7 @@ export default { session.close() } }, - saveCategorySettings: async (object, args, context, resolveInfo) => { + saveCategorySettings: async (_object, args, context, _resolveInfo) => { const { activeCategories } = args const { user: { id }, @@ -351,7 +351,7 @@ export default { session.close() } }, - updateOnlineStatus: async (object, args, context, resolveInfo) => { + updateOnlineStatus: async (_object, args, context, _resolveInfo) => { const { status } = args const { user: { id }, @@ -451,7 +451,7 @@ export default { }, }, User: { - emailNotificationSettings: async (parent, params, context, resolveInfo) => { + emailNotificationSettings: async (parent, _params, _context, _resolveInfo) => { return [ { type: 'post', diff --git a/backend/src/schema/resolvers/users/location.ts b/backend/src/schema/resolvers/users/location.ts index b663eebdf..e5ab85d75 100644 --- a/backend/src/schema/resolvers/users/location.ts +++ b/backend/src/schema/resolvers/users/location.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable promise/avoid-new */ /* eslint-disable promise/prefer-await-to-callbacks */ /* eslint-disable import/no-named-as-default */ diff --git a/backend/src/schema/resolvers/users/mutedUsers.spec.ts b/backend/src/schema/resolvers/users/mutedUsers.spec.ts index 1fda2b392..93c54625d 100644 --- a/backend/src/schema/resolvers/users/mutedUsers.spec.ts +++ b/backend/src/schema/resolvers/users/mutedUsers.spec.ts @@ -60,6 +60,7 @@ describe('mutedUsers', () => { it('throws permission error', async () => { const { query } = createTestClient(server) const result = await query({ query: mutedUserQuery }) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion expect(result.errors![0]).toHaveProperty('message', 'Not Authorized!') }) diff --git a/backend/src/server.ts b/backend/src/server.ts index 117e0c3b6..833313253 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable import/no-named-as-default-member */ import http from 'node:http' @@ -68,7 +69,7 @@ const createServer = (options?) => { context, schema: middleware(schema), subscriptions: { - onConnect: (connectionParams, webSocket) => { + onConnect: (connectionParams, _webSocket) => { return getContext(connectionParams) }, }, From b464a380ca5cea226f998b49d9e8f91b7899fa1a Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Mon, 21 Apr 2025 12:32:44 +0200 Subject: [PATCH 17/54] refactor(backend): lint @typescript-eslint/recommended-requiring-type-checking (#8407) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * lint @typescript-eslint/recommended * lint @typescript-eslint/recommended-requiring-type-checking fix type not detected locally due to wierd uuid typings missing save error not reported locally --------- Co-authored-by: Wolfgang Huß --- backend/.eslintrc.cjs | 2 +- backend/src/config/index.ts | 8 ++++++++ backend/src/db/admin.ts | 6 ++++++ backend/src/db/badges.ts | 4 ++++ backend/src/db/categories.ts | 6 ++++++ backend/src/db/compiler.ts | 2 ++ backend/src/db/data-branding.ts | 5 +++++ backend/src/db/factories.ts | 8 ++++++++ backend/src/db/migrate/store.ts | 8 ++++++++ backend/src/db/migrate/template.ts | 8 ++++++++ .../20200123150105-merge_duplicate_user_accounts.ts | 8 ++++++++ .../20200123150110-merge_duplicate_location_nodes.ts | 8 ++++++++ ...elationship_between_existing_blocked_relationships.ts | 8 ++++++++ .../20200206190233-swap_latitude_with_longitude.ts | 9 +++++++++ .../20200207080200-fulltext_index_for_tags.ts | 8 ++++++++ .../20200213230248-add_unique_index_to_image_url.ts | 8 ++++++++ .../20200312140328-bulk_upload_to_s3.ts | 7 +++++++ ...0200320200315-refactor_all_images_to_separate_type.ts | 8 ++++++++ ...323140300-remove_deleted_users_obsolete_attributes.ts | 8 ++++++++ ...323160336-remove_deleted_posts_obsolete_attributes.ts | 8 ++++++++ .../20200326160326-remove_dangling_image_urls.ts | 7 +++++++ .../1614023644903-add-clickedCount-to-posts.ts | 4 ++++ .../1614177130817-add-viewedTeaserCount-to-posts.ts | 4 ++++ .../db/migrations/20210506150512-add-donations-node.ts | 8 ++++++++ ...9-add-sendNotificationEmails-property-to-all-users.ts | 8 ++++++++ ...create_fulltext_indices_and_unique_keys_for_groups.ts | 8 ++++++++ .../migrations/20230320130345-fulltext-search-indexes.ts | 8 ++++++++ .../migrations/20230329150329-article-label-for-posts.ts | 8 ++++++++ .../migrations/20230608130637-add-postType-property.ts | 8 ++++++++ .../src/db/migrations/20231017141022-fix-event-dates.ts | 8 ++++++++ .../20250331130323-author-observes-own-post.ts | 8 ++++++++ .../migrations/20250331140313-commenter-observes-post.ts | 8 ++++++++ .../20250405030454-email-notification-settings.ts | 8 ++++++++ .../db/migrations/20250414220436-delete-old-badges.ts | 8 ++++++++ backend/src/db/neo4j.ts | 5 +++++ backend/src/db/reset-with-migrations.ts | 2 ++ backend/src/db/reset.ts | 2 ++ backend/src/db/seed.ts | 4 ++++ backend/src/db/seed/badges.ts | 3 +++ backend/src/helpers/asyncForEach.ts | 2 ++ backend/src/helpers/encryptPassword.ts | 3 +++ backend/src/helpers/jest.ts | 1 + backend/src/helpers/walkRecursive.ts | 5 +++++ backend/src/index.ts | 2 ++ backend/src/jwt/decode.spec.ts | 3 +++ backend/src/jwt/decode.ts | 4 ++++ backend/src/jwt/encode.spec.ts | 3 +++ backend/src/jwt/encode.ts | 4 ++++ backend/src/middleware/chatMiddleware.ts | 4 ++++ backend/src/middleware/excerptMiddleware.ts | 5 +++++ backend/src/middleware/hashtags/extractHashtags.spec.ts | 2 ++ backend/src/middleware/hashtags/extractHashtags.ts | 5 +++++ .../src/middleware/hashtags/hashtagsMiddleware.spec.ts | 4 ++++ backend/src/middleware/hashtags/hashtagsMiddleware.ts | 4 ++++ backend/src/middleware/helpers/cleanHtml.ts | 5 +++++ backend/src/middleware/helpers/email/sendMail.ts | 6 ++++++ .../src/middleware/helpers/email/templateBuilder.spec.ts | 6 ++++++ backend/src/middleware/helpers/email/templateBuilder.ts | 5 +++++ .../src/middleware/helpers/email/templates/de/index.ts | 1 + .../src/middleware/helpers/email/templates/en/index.ts | 1 + backend/src/middleware/helpers/email/templates/index.ts | 1 + backend/src/middleware/helpers/isUserOnline.spec.ts | 1 + backend/src/middleware/helpers/isUserOnline.ts | 3 +++ backend/src/middleware/includedFieldsMiddleware.ts | 4 ++++ backend/src/middleware/index.ts | 6 ++++++ backend/src/middleware/languages/languages.spec.ts | 3 +++ backend/src/middleware/languages/languages.ts | 6 ++++++ backend/src/middleware/login/loginMiddleware.ts | 4 ++++ .../notifications/mentions/extractMentionedUsers.ts | 1 + .../notifications/notificationsMiddleware.emails.spec.ts | 6 ++++++ .../notificationsMiddleware.followed-users.spec.ts | 5 +++++ .../notificationsMiddleware.mentions-in-groups.spec.ts | 6 ++++++ .../notificationsMiddleware.observing-posts.spec.ts | 4 ++++ .../notificationsMiddleware.online-status.spec.ts | 5 +++++ .../notificationsMiddleware.posts-in-groups.spec.ts | 6 ++++++ .../notifications/notificationsMiddleware.spec.ts | 6 ++++++ .../middleware/notifications/notificationsMiddleware.ts | 5 +++++ backend/src/middleware/orderByMiddleware.spec.ts | 3 +++ backend/src/middleware/orderByMiddleware.ts | 4 ++++ backend/src/middleware/permissionsMiddleware.spec.ts | 4 ++++ backend/src/middleware/permissionsMiddleware.ts | 6 ++++++ backend/src/middleware/sentryMiddleware.ts | 4 ++++ backend/src/middleware/sluggifyMiddleware.ts | 5 +++++ backend/src/middleware/slugify/uniqueSlug.ts | 4 ++++ backend/src/middleware/slugifyMiddleware.spec.ts | 4 ++++ .../middleware/softDelete/softDeleteMiddleware.spec.ts | 6 ++++++ .../src/middleware/softDelete/softDeleteMiddleware.ts | 5 +++++ backend/src/middleware/userInteractions.spec.ts | 3 +++ backend/src/middleware/userInteractions.ts | 5 +++++ .../middleware/validation/validationMiddleware.spec.ts | 3 +++ .../src/middleware/validation/validationMiddleware.ts | 7 +++++++ backend/src/middleware/xssMiddleware.ts | 5 +++++ backend/src/models/Category.ts | 1 + backend/src/models/Comment.ts | 1 + backend/src/models/Donations.ts | 1 + backend/src/models/Group.ts | 1 + backend/src/models/Post.ts | 1 + backend/src/models/Report.ts | 1 + backend/src/models/SocialMedia.ts | 1 + backend/src/models/User.spec.ts | 5 +++++ backend/src/models/User.ts | 1 + backend/src/models/index.ts | 2 ++ backend/src/schema/index.ts | 2 ++ backend/src/schema/resolvers/badges.spec.ts | 4 ++++ backend/src/schema/resolvers/badges.ts | 6 ++++++ backend/src/schema/resolvers/comments.spec.ts | 4 ++++ backend/src/schema/resolvers/comments.ts | 4 ++++ backend/src/schema/resolvers/donations.spec.ts | 4 ++++ backend/src/schema/resolvers/donations.ts | 4 ++++ backend/src/schema/resolvers/emails.spec.ts | 5 +++++ backend/src/schema/resolvers/emails.ts | 5 +++++ backend/src/schema/resolvers/embeds.spec.ts | 3 +++ backend/src/schema/resolvers/embeds.ts | 3 +++ backend/src/schema/resolvers/embeds/findProvider.ts | 5 +++++ backend/src/schema/resolvers/embeds/scraper.ts | 6 ++++++ backend/src/schema/resolvers/filter-posts.spec.ts | 3 +++ backend/src/schema/resolvers/follow.spec.ts | 4 ++++ backend/src/schema/resolvers/follow.ts | 4 ++++ backend/src/schema/resolvers/groups.spec.ts | 4 ++++ backend/src/schema/resolvers/groups.ts | 7 +++++++ backend/src/schema/resolvers/helpers/Resolver.ts | 7 +++++++ .../src/schema/resolvers/helpers/createPasswordReset.ts | 4 ++++ backend/src/schema/resolvers/helpers/databaseLogger.ts | 3 +++ backend/src/schema/resolvers/helpers/events.ts | 4 ++++ .../src/schema/resolvers/helpers/existingEmailAddress.ts | 4 ++++ .../src/schema/resolvers/helpers/filterForMutedUsers.ts | 4 ++++ .../src/schema/resolvers/helpers/filterInvisiblePosts.ts | 4 ++++ .../schema/resolvers/helpers/filterPostsOfMyGroups.ts | 4 ++++ backend/src/schema/resolvers/helpers/normalizeEmail.ts | 2 ++ backend/src/schema/resolvers/images/images.spec.ts | 7 +++++++ backend/src/schema/resolvers/images/images.ts | 7 +++++++ backend/src/schema/resolvers/inviteCodes.spec.ts | 3 +++ backend/src/schema/resolvers/inviteCodes.ts | 5 +++++ backend/src/schema/resolvers/locations.spec.ts | 3 +++ backend/src/schema/resolvers/locations.ts | 2 ++ backend/src/schema/resolvers/messages.spec.ts | 5 +++++ backend/src/schema/resolvers/messages.ts | 6 ++++++ backend/src/schema/resolvers/moderation.spec.ts | 4 ++++ backend/src/schema/resolvers/moderation.ts | 4 ++++ backend/src/schema/resolvers/notifications.spec.ts | 5 +++++ backend/src/schema/resolvers/notifications.ts | 6 ++++++ backend/src/schema/resolvers/observePosts.spec.ts | 3 +++ backend/src/schema/resolvers/passwordReset.spec.ts | 4 ++++ backend/src/schema/resolvers/passwordReset.ts | 6 ++++++ backend/src/schema/resolvers/posts.spec.ts | 5 +++++ backend/src/schema/resolvers/posts.ts | 6 ++++++ backend/src/schema/resolvers/postsInGroups.spec.ts | 4 ++++ backend/src/schema/resolvers/registration.spec.ts | 5 +++++ backend/src/schema/resolvers/registration.ts | 5 +++++ backend/src/schema/resolvers/reports.spec.ts | 4 ++++ backend/src/schema/resolvers/reports.ts | 5 +++++ backend/src/schema/resolvers/roles.ts | 1 + backend/src/schema/resolvers/rooms.spec.ts | 3 +++ backend/src/schema/resolvers/rooms.ts | 6 ++++++ backend/src/schema/resolvers/searches.spec.ts | 3 +++ backend/src/schema/resolvers/searches.ts | 6 ++++++ backend/src/schema/resolvers/searches/queryString.ts | 6 ++++++ backend/src/schema/resolvers/shout.spec.ts | 4 ++++ backend/src/schema/resolvers/shout.ts | 4 ++++ backend/src/schema/resolvers/socialMedia.spec.ts | 5 +++++ backend/src/schema/resolvers/socialMedia.ts | 4 ++++ backend/src/schema/resolvers/statistics.spec.ts | 4 ++++ backend/src/schema/resolvers/statistics.ts | 4 ++++ backend/src/schema/resolvers/transactions/inviteCodes.ts | 4 ++++ backend/src/schema/resolvers/userData.spec.ts | 3 +++ backend/src/schema/resolvers/userData.ts | 5 +++++ backend/src/schema/resolvers/user_management.spec.ts | 5 +++++ backend/src/schema/resolvers/user_management.ts | 7 +++++++ backend/src/schema/resolvers/users.spec.ts | 5 +++++ backend/src/schema/resolvers/users.ts | 7 +++++++ backend/src/schema/resolvers/users/location.spec.ts | 3 +++ backend/src/schema/resolvers/users/location.ts | 7 +++++++ backend/src/schema/resolvers/users/mutedUsers.spec.ts | 4 ++++ backend/src/schema/resolvers/viewedTeaserCount.spec.ts | 3 +++ backend/src/server.ts | 5 +++++ 175 files changed, 810 insertions(+), 1 deletion(-) diff --git a/backend/.eslintrc.cjs b/backend/.eslintrc.cjs index 37d4dda26..684c26948 100644 --- a/backend/.eslintrc.cjs +++ b/backend/.eslintrc.cjs @@ -180,7 +180,7 @@ module.exports = { files: ['*.ts', '*.tsx'], extends: [ 'plugin:@typescript-eslint/recommended', - // 'plugin:@typescript-eslint/recommended-requiring-type-checking', + 'plugin:@typescript-eslint/recommended-requiring-type-checking', // 'plugin:@typescript-eslint/strict', 'prettier', ], diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index db6b21460..cf07297df 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ /* eslint-disable n/no-process-env */ /* eslint-disable n/no-unpublished-require */ /* eslint-disable n/no-missing-require */ diff --git a/backend/src/db/admin.ts b/backend/src/db/admin.ts index f1575214f..ae6bca49e 100644 --- a/backend/src/db/admin.ts +++ b/backend/src/db/admin.ts @@ -1,3 +1,9 @@ +/* eslint-disable @typescript-eslint/no-floating-promises */ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ import { hashSync } from 'bcryptjs' import { v4 as uuid } from 'uuid' diff --git a/backend/src/db/badges.ts b/backend/src/db/badges.ts index a77f08731..b4e879357 100644 --- a/backend/src/db/badges.ts +++ b/backend/src/db/badges.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-floating-promises */ import { getNeode } from './neo4j' import { trophies, verification } from './seed/badges' diff --git a/backend/src/db/categories.ts b/backend/src/db/categories.ts index f550c4d94..b3774e7b9 100644 --- a/backend/src/db/categories.ts +++ b/backend/src/db/categories.ts @@ -1,3 +1,9 @@ +/* eslint-disable @typescript-eslint/no-floating-promises */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { categories } from '@constants/categories' import { getDriver } from './neo4j' diff --git a/backend/src/db/compiler.ts b/backend/src/db/compiler.ts index 26bf5a1e2..9c2140f2a 100644 --- a/backend/src/db/compiler.ts +++ b/backend/src/db/compiler.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable import/no-commonjs */ // eslint-disable-next-line n/no-unpublished-require, @typescript-eslint/no-var-requires const tsNode = require('ts-node') diff --git a/backend/src/db/data-branding.ts b/backend/src/db/data-branding.ts index ff84aca60..e9af41840 100644 --- a/backend/src/db/data-branding.ts +++ b/backend/src/db/data-branding.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-misused-promises */ +/* eslint-disable @typescript-eslint/no-floating-promises */ import { readdir } from 'node:fs/promises' import path from 'node:path' diff --git a/backend/src/db/factories.ts b/backend/src/db/factories.ts index 1ce8a3329..6839ae65c 100644 --- a/backend/src/db/factories.ts +++ b/backend/src/db/factories.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/unbound-method */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ import { faker } from '@faker-js/faker' import { hashSync } from 'bcryptjs' import { Factory } from 'rosie' diff --git a/backend/src/db/migrate/store.ts b/backend/src/db/migrate/store.ts index aa8bd66d1..947364e4d 100644 --- a/backend/src/db/migrate/store.ts +++ b/backend/src/db/migrate/store.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ import { getDriver, getNeode } from '@db/neo4j' class Store { diff --git a/backend/src/db/migrate/template.ts b/backend/src/db/migrate/template.ts index 6ce151e76..ce538f260 100644 --- a/backend/src/db/migrate/template.ts +++ b/backend/src/db/migrate/template.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ import { getDriver } from '@db/neo4j' export const description = '' diff --git a/backend/src/db/migrations-examples/20200123150105-merge_duplicate_user_accounts.ts b/backend/src/db/migrations-examples/20200123150105-merge_duplicate_user_accounts.ts index 45bdc4fe0..d13eeecf9 100644 --- a/backend/src/db/migrations-examples/20200123150105-merge_duplicate_user_accounts.ts +++ b/backend/src/db/migrations-examples/20200123150105-merge_duplicate_user_accounts.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ /* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable promise/prefer-await-to-callbacks */ import { throwError, concat } from 'rxjs' diff --git a/backend/src/db/migrations-examples/20200123150110-merge_duplicate_location_nodes.ts b/backend/src/db/migrations-examples/20200123150110-merge_duplicate_location_nodes.ts index 6aee51c83..249464257 100644 --- a/backend/src/db/migrations-examples/20200123150110-merge_duplicate_location_nodes.ts +++ b/backend/src/db/migrations-examples/20200123150110-merge_duplicate_location_nodes.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ /* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable promise/prefer-await-to-callbacks */ import { throwError, concat } from 'rxjs' diff --git a/backend/src/db/migrations-examples/20200127110135-create_muted_relationship_between_existing_blocked_relationships.ts b/backend/src/db/migrations-examples/20200127110135-create_muted_relationship_between_existing_blocked_relationships.ts index f24bbdee8..cfc00fcfe 100644 --- a/backend/src/db/migrations-examples/20200127110135-create_muted_relationship_between_existing_blocked_relationships.ts +++ b/backend/src/db/migrations-examples/20200127110135-create_muted_relationship_between_existing_blocked_relationships.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ import { getDriver } from '@db/neo4j' export const description = ` diff --git a/backend/src/db/migrations-examples/20200206190233-swap_latitude_with_longitude.ts b/backend/src/db/migrations-examples/20200206190233-swap_latitude_with_longitude.ts index 84e15f9fb..a1eab53fa 100644 --- a/backend/src/db/migrations-examples/20200206190233-swap_latitude_with_longitude.ts +++ b/backend/src/db/migrations-examples/20200206190233-swap_latitude_with_longitude.ts @@ -1,3 +1,12 @@ +/* eslint-disable @typescript-eslint/no-floating-promises */ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ import { getDriver } from '@db/neo4j' export const description = ` diff --git a/backend/src/db/migrations-examples/20200207080200-fulltext_index_for_tags.ts b/backend/src/db/migrations-examples/20200207080200-fulltext_index_for_tags.ts index 79b46a1ff..f7bcb0810 100644 --- a/backend/src/db/migrations-examples/20200207080200-fulltext_index_for_tags.ts +++ b/backend/src/db/migrations-examples/20200207080200-fulltext_index_for_tags.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ import { getDriver } from '@db/neo4j' export const description = diff --git a/backend/src/db/migrations-examples/20200213230248-add_unique_index_to_image_url.ts b/backend/src/db/migrations-examples/20200213230248-add_unique_index_to_image_url.ts index 2a30d769e..a22a38127 100644 --- a/backend/src/db/migrations-examples/20200213230248-add_unique_index_to_image_url.ts +++ b/backend/src/db/migrations-examples/20200213230248-add_unique_index_to_image_url.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ import { getDriver } from '@db/neo4j' export const description = ` diff --git a/backend/src/db/migrations-examples/20200312140328-bulk_upload_to_s3.ts b/backend/src/db/migrations-examples/20200312140328-bulk_upload_to_s3.ts index f0531b6c8..9d97aab7e 100644 --- a/backend/src/db/migrations-examples/20200312140328-bulk_upload_to_s3.ts +++ b/backend/src/db/migrations-examples/20200312140328-bulk_upload_to_s3.ts @@ -1,3 +1,10 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable security/detect-non-literal-fs-filename */ import https from 'https' import { existsSync, createReadStream } from 'node:fs' diff --git a/backend/src/db/migrations-examples/20200320200315-refactor_all_images_to_separate_type.ts b/backend/src/db/migrations-examples/20200320200315-refactor_all_images_to_separate_type.ts index 355eb8476..61be45099 100644 --- a/backend/src/db/migrations-examples/20200320200315-refactor_all_images_to_separate_type.ts +++ b/backend/src/db/migrations-examples/20200320200315-refactor_all_images_to_separate_type.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ /* eslint-disable no-console */ import { getDriver } from '@db/neo4j' diff --git a/backend/src/db/migrations-examples/20200323140300-remove_deleted_users_obsolete_attributes.ts b/backend/src/db/migrations-examples/20200323140300-remove_deleted_users_obsolete_attributes.ts index 5ce75ab28..36b29f477 100644 --- a/backend/src/db/migrations-examples/20200323140300-remove_deleted_users_obsolete_attributes.ts +++ b/backend/src/db/migrations-examples/20200323140300-remove_deleted_users_obsolete_attributes.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ import { getDriver } from '@db/neo4j' export const description = diff --git a/backend/src/db/migrations-examples/20200323160336-remove_deleted_posts_obsolete_attributes.ts b/backend/src/db/migrations-examples/20200323160336-remove_deleted_posts_obsolete_attributes.ts index a2b5ff159..1a3e97dba 100644 --- a/backend/src/db/migrations-examples/20200323160336-remove_deleted_posts_obsolete_attributes.ts +++ b/backend/src/db/migrations-examples/20200323160336-remove_deleted_posts_obsolete_attributes.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ import { getDriver } from '@db/neo4j' export const description = diff --git a/backend/src/db/migrations-examples/20200326160326-remove_dangling_image_urls.ts b/backend/src/db/migrations-examples/20200326160326-remove_dangling_image_urls.ts index 0190ead48..f5fbd24de 100644 --- a/backend/src/db/migrations-examples/20200326160326-remove_dangling_image_urls.ts +++ b/backend/src/db/migrations-examples/20200326160326-remove_dangling_image_urls.ts @@ -1,3 +1,10 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable security/detect-non-literal-fs-filename */ import { existsSync } from 'node:fs' diff --git a/backend/src/db/migrations/1614023644903-add-clickedCount-to-posts.ts b/backend/src/db/migrations/1614023644903-add-clickedCount-to-posts.ts index 65fb33a68..9bb7ab996 100644 --- a/backend/src/db/migrations/1614023644903-add-clickedCount-to-posts.ts +++ b/backend/src/db/migrations/1614023644903-add-clickedCount-to-posts.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { getDriver } from '@db/neo4j' export const description = ` diff --git a/backend/src/db/migrations/1614177130817-add-viewedTeaserCount-to-posts.ts b/backend/src/db/migrations/1614177130817-add-viewedTeaserCount-to-posts.ts index f4fd7cda4..9ebfee85c 100644 --- a/backend/src/db/migrations/1614177130817-add-viewedTeaserCount-to-posts.ts +++ b/backend/src/db/migrations/1614177130817-add-viewedTeaserCount-to-posts.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { getDriver } from '@db/neo4j' export const description = ` diff --git a/backend/src/db/migrations/20210506150512-add-donations-node.ts b/backend/src/db/migrations/20210506150512-add-donations-node.ts index fcb4d816c..78919d46e 100644 --- a/backend/src/db/migrations/20210506150512-add-donations-node.ts +++ b/backend/src/db/migrations/20210506150512-add-donations-node.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ import { v4 as uuid } from 'uuid' import { getDriver } from '@db/neo4j' diff --git a/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.ts b/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.ts index 804d12097..e4ed26095 100644 --- a/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.ts +++ b/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ import { getDriver } from '@db/neo4j' export const description = '' diff --git a/backend/src/db/migrations/20220803060819-create_fulltext_indices_and_unique_keys_for_groups.ts b/backend/src/db/migrations/20220803060819-create_fulltext_indices_and_unique_keys_for_groups.ts index 815c695e9..9fa0ffcd2 100644 --- a/backend/src/db/migrations/20220803060819-create_fulltext_indices_and_unique_keys_for_groups.ts +++ b/backend/src/db/migrations/20220803060819-create_fulltext_indices_and_unique_keys_for_groups.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ import { getDriver } from '@db/neo4j' export const description = ` diff --git a/backend/src/db/migrations/20230320130345-fulltext-search-indexes.ts b/backend/src/db/migrations/20230320130345-fulltext-search-indexes.ts index 706e952c0..bf3541e7c 100644 --- a/backend/src/db/migrations/20230320130345-fulltext-search-indexes.ts +++ b/backend/src/db/migrations/20230320130345-fulltext-search-indexes.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ import { getDriver } from '@db/neo4j' export const description = '' diff --git a/backend/src/db/migrations/20230329150329-article-label-for-posts.ts b/backend/src/db/migrations/20230329150329-article-label-for-posts.ts index d11935833..1c1259ca0 100644 --- a/backend/src/db/migrations/20230329150329-article-label-for-posts.ts +++ b/backend/src/db/migrations/20230329150329-article-label-for-posts.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ import { getDriver } from '@db/neo4j' export const description = 'Add to all existing posts the Article label' diff --git a/backend/src/db/migrations/20230608130637-add-postType-property.ts b/backend/src/db/migrations/20230608130637-add-postType-property.ts index c9941f713..e4f1033b5 100644 --- a/backend/src/db/migrations/20230608130637-add-postType-property.ts +++ b/backend/src/db/migrations/20230608130637-add-postType-property.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ import { getDriver } from '@db/neo4j' export const description = 'Add postType property Article to all posts' diff --git a/backend/src/db/migrations/20231017141022-fix-event-dates.ts b/backend/src/db/migrations/20231017141022-fix-event-dates.ts index 4e9764f63..b3d7d14bd 100644 --- a/backend/src/db/migrations/20231017141022-fix-event-dates.ts +++ b/backend/src/db/migrations/20231017141022-fix-event-dates.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ import { getDriver } from '@db/neo4j' export const description = ` diff --git a/backend/src/db/migrations/20250331130323-author-observes-own-post.ts b/backend/src/db/migrations/20250331130323-author-observes-own-post.ts index 700f69a64..e088e12c1 100644 --- a/backend/src/db/migrations/20250331130323-author-observes-own-post.ts +++ b/backend/src/db/migrations/20250331130323-author-observes-own-post.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ import { getDriver } from '@db/neo4j' export const description = ` diff --git a/backend/src/db/migrations/20250331140313-commenter-observes-post.ts b/backend/src/db/migrations/20250331140313-commenter-observes-post.ts index 6cd97f4f2..f3f358f20 100644 --- a/backend/src/db/migrations/20250331140313-commenter-observes-post.ts +++ b/backend/src/db/migrations/20250331140313-commenter-observes-post.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ import { getDriver } from '@db/neo4j' export const description = ` diff --git a/backend/src/db/migrations/20250405030454-email-notification-settings.ts b/backend/src/db/migrations/20250405030454-email-notification-settings.ts index 2030aea0f..f17c88fa9 100644 --- a/backend/src/db/migrations/20250405030454-email-notification-settings.ts +++ b/backend/src/db/migrations/20250405030454-email-notification-settings.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ import { getDriver } from '@db/neo4j' export const description = diff --git a/backend/src/db/migrations/20250414220436-delete-old-badges.ts b/backend/src/db/migrations/20250414220436-delete-old-badges.ts index ce37590e8..9c1d2b8e1 100644 --- a/backend/src/db/migrations/20250414220436-delete-old-badges.ts +++ b/backend/src/db/migrations/20250414220436-delete-old-badges.ts @@ -1,3 +1,11 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable security/detect-non-literal-fs-filename */ import { getDriver } from '@db/neo4j' export const description = '' diff --git a/backend/src/db/neo4j.ts b/backend/src/db/neo4j.ts index c94c552f0..b7c0eec56 100644 --- a/backend/src/db/neo4j.ts +++ b/backend/src/db/neo4j.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable import/no-named-as-default-member */ import neo4j from 'neo4j-driver' import Neode from 'neode' diff --git a/backend/src/db/reset-with-migrations.ts b/backend/src/db/reset-with-migrations.ts index fc3d86b09..78db831ce 100644 --- a/backend/src/db/reset-with-migrations.ts +++ b/backend/src/db/reset-with-migrations.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-floating-promises */ /* eslint-disable n/no-process-exit */ import CONFIG from '@config/index' diff --git a/backend/src/db/reset.ts b/backend/src/db/reset.ts index 0f316faf8..a381799c6 100644 --- a/backend/src/db/reset.ts +++ b/backend/src/db/reset.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-floating-promises */ /* eslint-disable n/no-process-exit */ import CONFIG from '@config/index' diff --git a/backend/src/db/seed.ts b/backend/src/db/seed.ts index a1dc25b11..b727c85e8 100644 --- a/backend/src/db/seed.ts +++ b/backend/src/db/seed.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-floating-promises */ /* eslint-disable n/no-process-exit */ import { faker } from '@faker-js/faker' import { createTestClient } from 'apollo-server-testing' diff --git a/backend/src/db/seed/badges.ts b/backend/src/db/seed/badges.ts index 2ec4bb21b..ce8bff9ba 100644 --- a/backend/src/db/seed/badges.ts +++ b/backend/src/db/seed/badges.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import Factory from '@db/factories' export const trophies = async () => { diff --git a/backend/src/helpers/asyncForEach.ts b/backend/src/helpers/asyncForEach.ts index 00b0f85a3..354f2cd07 100644 --- a/backend/src/helpers/asyncForEach.ts +++ b/backend/src/helpers/asyncForEach.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable promise/prefer-await-to-callbacks */ /* eslint-disable security/detect-object-injection */ /** diff --git a/backend/src/helpers/encryptPassword.ts b/backend/src/helpers/encryptPassword.ts index 657dee98a..d8d08c23c 100644 --- a/backend/src/helpers/encryptPassword.ts +++ b/backend/src/helpers/encryptPassword.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ import { hashSync } from 'bcryptjs' export default function (args) { diff --git a/backend/src/helpers/jest.ts b/backend/src/helpers/jest.ts index f1a0deb15..5594eb348 100644 --- a/backend/src/helpers/jest.ts +++ b/backend/src/helpers/jest.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable promise/avoid-new */ // sometime we have to wait to check a db state by having a look into the db in a certain moment // or we wait a bit to check if we missed to set an await somewhere diff --git a/backend/src/helpers/walkRecursive.ts b/backend/src/helpers/walkRecursive.ts index 4937f61bb..5874ca3af 100644 --- a/backend/src/helpers/walkRecursive.ts +++ b/backend/src/helpers/walkRecursive.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable promise/prefer-await-to-callbacks */ /* eslint-disable security/detect-object-injection */ /** diff --git a/backend/src/index.ts b/backend/src/index.ts index 35c215803..3da4ec7ae 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ import CONFIG from './config' import createServer from './server' diff --git a/backend/src/jwt/decode.spec.ts b/backend/src/jwt/decode.spec.ts index 29783bc6b..34dd86d68 100644 --- a/backend/src/jwt/decode.spec.ts +++ b/backend/src/jwt/decode.spec.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import Factory, { cleanDatabase } from '@db/factories' import { getDriver, getNeode } from '@db/neo4j' diff --git a/backend/src/jwt/decode.ts b/backend/src/jwt/decode.ts index b4424e660..0a433d38f 100644 --- a/backend/src/jwt/decode.ts +++ b/backend/src/jwt/decode.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import jwt from 'jsonwebtoken' import CONFIG from '@config/index' diff --git a/backend/src/jwt/encode.spec.ts b/backend/src/jwt/encode.spec.ts index 55c74bf8d..8121118f3 100644 --- a/backend/src/jwt/encode.spec.ts +++ b/backend/src/jwt/encode.spec.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import jwt from 'jsonwebtoken' import CONFIG from '@config/index' diff --git a/backend/src/jwt/encode.ts b/backend/src/jwt/encode.ts index 110111faf..742bf438b 100644 --- a/backend/src/jwt/encode.ts +++ b/backend/src/jwt/encode.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import jwt from 'jsonwebtoken' import CONFIG from '@config/index' diff --git a/backend/src/middleware/chatMiddleware.ts b/backend/src/middleware/chatMiddleware.ts index 8ae252e13..17d01fd95 100644 --- a/backend/src/middleware/chatMiddleware.ts +++ b/backend/src/middleware/chatMiddleware.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { isArray } from 'lodash' const setRoomProps = (room) => { diff --git a/backend/src/middleware/excerptMiddleware.ts b/backend/src/middleware/excerptMiddleware.ts index f903dd01c..7a865be90 100644 --- a/backend/src/middleware/excerptMiddleware.ts +++ b/backend/src/middleware/excerptMiddleware.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ import trunc from 'trunc-html' import { DESCRIPTION_EXCERPT_HTML_LENGTH } from '@constants/groups' diff --git a/backend/src/middleware/hashtags/extractHashtags.spec.ts b/backend/src/middleware/hashtags/extractHashtags.spec.ts index 739c7de54..134ede761 100644 --- a/backend/src/middleware/hashtags/extractHashtags.spec.ts +++ b/backend/src/middleware/hashtags/extractHashtags.spec.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ import extractHashtags from './extractHashtags' describe('extractHashtags', () => { diff --git a/backend/src/middleware/hashtags/extractHashtags.ts b/backend/src/middleware/hashtags/extractHashtags.ts index 908bb3080..26b224a9a 100644 --- a/backend/src/middleware/hashtags/extractHashtags.ts +++ b/backend/src/middleware/hashtags/extractHashtags.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { load } from 'cheerio' // eslint-disable-next-line import/extensions import { exec, build } from 'xregexp/xregexp-all.js' diff --git a/backend/src/middleware/hashtags/hashtagsMiddleware.spec.ts b/backend/src/middleware/hashtags/hashtagsMiddleware.spec.ts index 2bb617a3d..4f0d57303 100644 --- a/backend/src/middleware/hashtags/hashtagsMiddleware.spec.ts +++ b/backend/src/middleware/hashtags/hashtagsMiddleware.spec.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/middleware/hashtags/hashtagsMiddleware.ts b/backend/src/middleware/hashtags/hashtagsMiddleware.ts index 76939d59d..2f53ee1a5 100644 --- a/backend/src/middleware/hashtags/hashtagsMiddleware.ts +++ b/backend/src/middleware/hashtags/hashtagsMiddleware.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ import extractHashtags from './extractHashtags' const updateHashtagsOfPost = async (postId, hashtags, context) => { diff --git a/backend/src/middleware/helpers/cleanHtml.ts b/backend/src/middleware/helpers/cleanHtml.ts index e72746fcf..d429f8f9d 100644 --- a/backend/src/middleware/helpers/cleanHtml.ts +++ b/backend/src/middleware/helpers/cleanHtml.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable security/detect-unsafe-regex */ import linkifyHtml from 'linkify-html' import sanitizeHtml from 'sanitize-html' diff --git a/backend/src/middleware/helpers/email/sendMail.ts b/backend/src/middleware/helpers/email/sendMail.ts index b8c5cfb00..a7d223f1c 100644 --- a/backend/src/middleware/helpers/email/sendMail.ts +++ b/backend/src/middleware/helpers/email/sendMail.ts @@ -1,3 +1,9 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-plus-operands */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import nodemailer from 'nodemailer' import { htmlToText } from 'nodemailer-html-to-text' diff --git a/backend/src/middleware/helpers/email/templateBuilder.spec.ts b/backend/src/middleware/helpers/email/templateBuilder.spec.ts index 9dbfca91f..85608b55a 100644 --- a/backend/src/middleware/helpers/email/templateBuilder.spec.ts +++ b/backend/src/middleware/helpers/email/templateBuilder.spec.ts @@ -1,3 +1,9 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ import CONFIG from '@config/index' import logosWebapp from '@config/logos' diff --git a/backend/src/middleware/helpers/email/templateBuilder.ts b/backend/src/middleware/helpers/email/templateBuilder.ts index bd44716fe..ffceb49f6 100644 --- a/backend/src/middleware/helpers/email/templateBuilder.ts +++ b/backend/src/middleware/helpers/email/templateBuilder.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable import/no-namespace */ import mustache from 'mustache' diff --git a/backend/src/middleware/helpers/email/templates/de/index.ts b/backend/src/middleware/helpers/email/templates/de/index.ts index 6f0803bc7..408bbb34d 100644 --- a/backend/src/middleware/helpers/email/templates/de/index.ts +++ b/backend/src/middleware/helpers/email/templates/de/index.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable security/detect-non-literal-fs-filename */ import fs from 'node:fs' import path from 'node:path' diff --git a/backend/src/middleware/helpers/email/templates/en/index.ts b/backend/src/middleware/helpers/email/templates/en/index.ts index 6f0803bc7..408bbb34d 100644 --- a/backend/src/middleware/helpers/email/templates/en/index.ts +++ b/backend/src/middleware/helpers/email/templates/en/index.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable security/detect-non-literal-fs-filename */ import fs from 'node:fs' import path from 'node:path' diff --git a/backend/src/middleware/helpers/email/templates/index.ts b/backend/src/middleware/helpers/email/templates/index.ts index 79de6b8ae..f481516db 100644 --- a/backend/src/middleware/helpers/email/templates/index.ts +++ b/backend/src/middleware/helpers/email/templates/index.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable security/detect-non-literal-fs-filename */ import fs from 'node:fs' import path from 'node:path' diff --git a/backend/src/middleware/helpers/isUserOnline.spec.ts b/backend/src/middleware/helpers/isUserOnline.spec.ts index 62ed17f79..de4c840e3 100644 --- a/backend/src/middleware/helpers/isUserOnline.spec.ts +++ b/backend/src/middleware/helpers/isUserOnline.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { isUserOnline } from './isUserOnline' let user diff --git a/backend/src/middleware/helpers/isUserOnline.ts b/backend/src/middleware/helpers/isUserOnline.ts index 23ddeb0dc..b2d0b601f 100644 --- a/backend/src/middleware/helpers/isUserOnline.ts +++ b/backend/src/middleware/helpers/isUserOnline.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ export const isUserOnline = (user) => { // Is Recipient considered online const lastActive = new Date(user.lastActiveAt).getTime() diff --git a/backend/src/middleware/includedFieldsMiddleware.ts b/backend/src/middleware/includedFieldsMiddleware.ts index fd95029b0..4b0ab1b1e 100644 --- a/backend/src/middleware/includedFieldsMiddleware.ts +++ b/backend/src/middleware/includedFieldsMiddleware.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ import cloneDeep from 'lodash/cloneDeep' const _includeFieldsRecursively = (selectionSet, includedFields) => { diff --git a/backend/src/middleware/index.ts b/backend/src/middleware/index.ts index 225e02209..7ce53663c 100644 --- a/backend/src/middleware/index.ts +++ b/backend/src/middleware/index.ts @@ -1,3 +1,9 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable security/detect-object-injection */ import { applyMiddleware } from 'graphql-middleware' diff --git a/backend/src/middleware/languages/languages.spec.ts b/backend/src/middleware/languages/languages.spec.ts index ca77acac8..11ebf3a41 100644 --- a/backend/src/middleware/languages/languages.spec.ts +++ b/backend/src/middleware/languages/languages.spec.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/middleware/languages/languages.ts b/backend/src/middleware/languages/languages.ts index 6149b90d5..fb8c51a1f 100644 --- a/backend/src/middleware/languages/languages.ts +++ b/backend/src/middleware/languages/languages.ts @@ -1,3 +1,9 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/await-thenable */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import LanguageDetect from 'languagedetect' import { removeHtmlTags } from '@middleware/helpers/cleanHtml' diff --git a/backend/src/middleware/login/loginMiddleware.ts b/backend/src/middleware/login/loginMiddleware.ts index 04d189b4b..b67e5f60a 100644 --- a/backend/src/middleware/login/loginMiddleware.ts +++ b/backend/src/middleware/login/loginMiddleware.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { sendMail } from '@middleware/helpers/email/sendMail' import { signupTemplate, diff --git a/backend/src/middleware/notifications/mentions/extractMentionedUsers.ts b/backend/src/middleware/notifications/mentions/extractMentionedUsers.ts index b7dc0fed1..ccb7dd1be 100644 --- a/backend/src/middleware/notifications/mentions/extractMentionedUsers.ts +++ b/backend/src/middleware/notifications/mentions/extractMentionedUsers.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ import { load } from 'cheerio' export default (content?) => { diff --git a/backend/src/middleware/notifications/notificationsMiddleware.emails.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.emails.spec.ts index 497bf4c54..097917d1e 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.emails.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.emails.spec.ts @@ -1,3 +1,9 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/middleware/notifications/notificationsMiddleware.followed-users.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.followed-users.spec.ts index 4d4b0e872..c2b1419f4 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.followed-users.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.followed-users.spec.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts index 49763b4c3..5fce124d9 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts @@ -1,3 +1,9 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/middleware/notifications/notificationsMiddleware.observing-posts.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.observing-posts.spec.ts index e10d61d9f..2c73d2beb 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.observing-posts.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.observing-posts.spec.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/middleware/notifications/notificationsMiddleware.online-status.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.online-status.spec.ts index acc446d6b..da9a6b250 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.online-status.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.online-status.spec.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts index ad336596d..0e4ccad5a 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts @@ -1,3 +1,9 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/middleware/notifications/notificationsMiddleware.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.spec.ts index c58acc5e2..859a67544 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.spec.ts @@ -1,3 +1,9 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/middleware/notifications/notificationsMiddleware.ts b/backend/src/middleware/notifications/notificationsMiddleware.ts index a8d95b284..5d2febffd 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable security/detect-object-injection */ import { sendMail } from '@middleware/helpers/email/sendMail' import { diff --git a/backend/src/middleware/orderByMiddleware.spec.ts b/backend/src/middleware/orderByMiddleware.spec.ts index 9534af76d..1ae15b26a 100644 --- a/backend/src/middleware/orderByMiddleware.spec.ts +++ b/backend/src/middleware/orderByMiddleware.spec.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/middleware/orderByMiddleware.ts b/backend/src/middleware/orderByMiddleware.ts index 64eac8b74..9b437a5e9 100644 --- a/backend/src/middleware/orderByMiddleware.ts +++ b/backend/src/middleware/orderByMiddleware.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ import cloneDeep from 'lodash/cloneDeep' const defaultOrderBy = (resolve, root, args, context, resolveInfo) => { diff --git a/backend/src/middleware/permissionsMiddleware.spec.ts b/backend/src/middleware/permissionsMiddleware.spec.ts index 81d73bae8..834e9888a 100644 --- a/backend/src/middleware/permissionsMiddleware.spec.ts +++ b/backend/src/middleware/permissionsMiddleware.spec.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/middleware/permissionsMiddleware.ts b/backend/src/middleware/permissionsMiddleware.ts index 5ba9c8e4c..a17072c4b 100644 --- a/backend/src/middleware/permissionsMiddleware.ts +++ b/backend/src/middleware/permissionsMiddleware.ts @@ -1,3 +1,9 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { rule, shield, deny, allow, or, and } from 'graphql-shield' import CONFIG from '@config/index' diff --git a/backend/src/middleware/sentryMiddleware.ts b/backend/src/middleware/sentryMiddleware.ts index dab0c8353..6577be4ee 100644 --- a/backend/src/middleware/sentryMiddleware.ts +++ b/backend/src/middleware/sentryMiddleware.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ import { sentry } from 'graphql-middleware-sentry' import CONFIG from '@config/index' diff --git a/backend/src/middleware/sluggifyMiddleware.ts b/backend/src/middleware/sluggifyMiddleware.ts index bbe47c9aa..92c2c1367 100644 --- a/backend/src/middleware/sluggifyMiddleware.ts +++ b/backend/src/middleware/sluggifyMiddleware.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ import uniqueSlug from './slugify/uniqueSlug' const isUniqueFor = (context, type) => { diff --git a/backend/src/middleware/slugify/uniqueSlug.ts b/backend/src/middleware/slugify/uniqueSlug.ts index 41d58ece3..e24b15eb3 100644 --- a/backend/src/middleware/slugify/uniqueSlug.ts +++ b/backend/src/middleware/slugify/uniqueSlug.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import slugify from 'slug' export default async function uniqueSlug(string, isUnique) { diff --git a/backend/src/middleware/slugifyMiddleware.spec.ts b/backend/src/middleware/slugifyMiddleware.spec.ts index 9e55d54b1..496a90568 100644 --- a/backend/src/middleware/slugifyMiddleware.spec.ts +++ b/backend/src/middleware/slugifyMiddleware.spec.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import Factory, { cleanDatabase } from '@db/factories' diff --git a/backend/src/middleware/softDelete/softDeleteMiddleware.spec.ts b/backend/src/middleware/softDelete/softDeleteMiddleware.spec.ts index fa62ed101..6a718fdde 100644 --- a/backend/src/middleware/softDelete/softDeleteMiddleware.spec.ts +++ b/backend/src/middleware/softDelete/softDeleteMiddleware.spec.ts @@ -1,3 +1,9 @@ +/* eslint-disable @typescript-eslint/await-thenable */ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/middleware/softDelete/softDeleteMiddleware.ts b/backend/src/middleware/softDelete/softDeleteMiddleware.ts index 2e1f60251..4120733ff 100644 --- a/backend/src/middleware/softDelete/softDeleteMiddleware.ts +++ b/backend/src/middleware/softDelete/softDeleteMiddleware.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ const isModerator = ({ user }) => { return user && (user.role === 'moderator' || user.role === 'admin') } diff --git a/backend/src/middleware/userInteractions.spec.ts b/backend/src/middleware/userInteractions.spec.ts index 37b5401e3..fc096c2b7 100644 --- a/backend/src/middleware/userInteractions.spec.ts +++ b/backend/src/middleware/userInteractions.spec.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/middleware/userInteractions.ts b/backend/src/middleware/userInteractions.ts index 62e8e47f7..bb850a650 100644 --- a/backend/src/middleware/userInteractions.ts +++ b/backend/src/middleware/userInteractions.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ const createRelatedCypher = (relation) => ` MATCH (user:User { id: $currentUser}) MATCH (post:Post { id: $postId}) diff --git a/backend/src/middleware/validation/validationMiddleware.spec.ts b/backend/src/middleware/validation/validationMiddleware.spec.ts index 8e4b4329f..3d3cd9bda 100644 --- a/backend/src/middleware/validation/validationMiddleware.spec.ts +++ b/backend/src/middleware/validation/validationMiddleware.spec.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/middleware/validation/validationMiddleware.ts b/backend/src/middleware/validation/validationMiddleware.ts index 072f2d7b9..317595ee3 100644 --- a/backend/src/middleware/validation/validationMiddleware.ts +++ b/backend/src/middleware/validation/validationMiddleware.ts @@ -1,3 +1,10 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { UserInputError } from 'apollo-server' const COMMENT_MIN_LENGTH = 1 diff --git a/backend/src/middleware/xssMiddleware.ts b/backend/src/middleware/xssMiddleware.ts index 3ed310b40..31ded633c 100644 --- a/backend/src/middleware/xssMiddleware.ts +++ b/backend/src/middleware/xssMiddleware.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ import walkRecursive from '@helpers/walkRecursive' import { cleanHtml } from './helpers/cleanHtml' diff --git a/backend/src/models/Category.ts b/backend/src/models/Category.ts index 9a3f47fd0..f61d5aaab 100644 --- a/backend/src/models/Category.ts +++ b/backend/src/models/Category.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { v4 as uuid } from 'uuid' export default { diff --git a/backend/src/models/Comment.ts b/backend/src/models/Comment.ts index f4548f0c2..f05cb7ccc 100644 --- a/backend/src/models/Comment.ts +++ b/backend/src/models/Comment.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { v4 as uuid } from 'uuid' export default { diff --git a/backend/src/models/Donations.ts b/backend/src/models/Donations.ts index 742bfb569..61113702d 100644 --- a/backend/src/models/Donations.ts +++ b/backend/src/models/Donations.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { v4 as uuid } from 'uuid' export default { diff --git a/backend/src/models/Group.ts b/backend/src/models/Group.ts index a75ad518f..cff388a0a 100644 --- a/backend/src/models/Group.ts +++ b/backend/src/models/Group.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { v4 as uuid } from 'uuid' export default { diff --git a/backend/src/models/Post.ts b/backend/src/models/Post.ts index 75081b728..466e8a21d 100644 --- a/backend/src/models/Post.ts +++ b/backend/src/models/Post.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { v4 as uuid } from 'uuid' export default { diff --git a/backend/src/models/Report.ts b/backend/src/models/Report.ts index 3e001746b..07e8a79c1 100644 --- a/backend/src/models/Report.ts +++ b/backend/src/models/Report.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { v4 as uuid } from 'uuid' export default { diff --git a/backend/src/models/SocialMedia.ts b/backend/src/models/SocialMedia.ts index 6010c97bb..86f2f90be 100644 --- a/backend/src/models/SocialMedia.ts +++ b/backend/src/models/SocialMedia.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { v4 as uuid } from 'uuid' export default { diff --git a/backend/src/models/User.spec.ts b/backend/src/models/User.spec.ts index 3fde03462..7d2c584b5 100644 --- a/backend/src/models/User.spec.ts +++ b/backend/src/models/User.spec.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { cleanDatabase } from '@db/factories' import { getNeode, getDriver } from '@db/neo4j' diff --git a/backend/src/models/User.ts b/backend/src/models/User.ts index 77a37c3c1..611b6a984 100644 --- a/backend/src/models/User.ts +++ b/backend/src/models/User.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { v4 as uuid } from 'uuid' export default { diff --git a/backend/src/models/index.ts b/backend/src/models/index.ts index 74917e77e..6bbdab338 100644 --- a/backend/src/models/index.ts +++ b/backend/src/models/index.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable n/no-missing-require */ /* eslint-disable n/global-require */ diff --git a/backend/src/schema/index.ts b/backend/src/schema/index.ts index e043bc243..702b4a001 100644 --- a/backend/src/schema/index.ts +++ b/backend/src/schema/index.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { makeAugmentedSchema } from 'neo4j-graphql-js' import resolvers from './resolvers' diff --git a/backend/src/schema/resolvers/badges.spec.ts b/backend/src/schema/resolvers/badges.spec.ts index c9d297547..17fe46c99 100644 --- a/backend/src/schema/resolvers/badges.spec.ts +++ b/backend/src/schema/resolvers/badges.spec.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/badges.ts b/backend/src/schema/resolvers/badges.ts index f0ae4f8e2..430e3bf75 100644 --- a/backend/src/schema/resolvers/badges.ts +++ b/backend/src/schema/resolvers/badges.ts @@ -1,3 +1,9 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { neo4jgraphql } from 'neo4j-graphql-js' export default { diff --git a/backend/src/schema/resolvers/comments.spec.ts b/backend/src/schema/resolvers/comments.spec.ts index e92daf86e..432723bae 100644 --- a/backend/src/schema/resolvers/comments.spec.ts +++ b/backend/src/schema/resolvers/comments.spec.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/comments.ts b/backend/src/schema/resolvers/comments.ts index 4072f470c..3400b1d23 100644 --- a/backend/src/schema/resolvers/comments.ts +++ b/backend/src/schema/resolvers/comments.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { v4 as uuid } from 'uuid' import Resolver from './helpers/Resolver' diff --git a/backend/src/schema/resolvers/donations.spec.ts b/backend/src/schema/resolvers/donations.spec.ts index ef2070d4e..8e58153c1 100644 --- a/backend/src/schema/resolvers/donations.spec.ts +++ b/backend/src/schema/resolvers/donations.spec.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/donations.ts b/backend/src/schema/resolvers/donations.ts index d077e7bed..017a97f5f 100644 --- a/backend/src/schema/resolvers/donations.ts +++ b/backend/src/schema/resolvers/donations.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ export default { Query: { Donations: async (_parent, _params, context, _resolveInfo) => { diff --git a/backend/src/schema/resolvers/emails.spec.ts b/backend/src/schema/resolvers/emails.spec.ts index 63141a3fc..d16adbf5d 100644 --- a/backend/src/schema/resolvers/emails.spec.ts +++ b/backend/src/schema/resolvers/emails.spec.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/await-thenable */ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/emails.ts b/backend/src/schema/resolvers/emails.ts index 0638ec634..d4313558e 100644 --- a/backend/src/schema/resolvers/emails.ts +++ b/backend/src/schema/resolvers/emails.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { UserInputError } from 'apollo-server' // eslint-disable-next-line import/extensions import Validator from 'neode/build/Services/Validator.js' diff --git a/backend/src/schema/resolvers/embeds.spec.ts b/backend/src/schema/resolvers/embeds.spec.ts index 7a49986cf..a3b33f81f 100644 --- a/backend/src/schema/resolvers/embeds.spec.ts +++ b/backend/src/schema/resolvers/embeds.spec.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ import fs from 'node:fs' import path from 'node:path' diff --git a/backend/src/schema/resolvers/embeds.ts b/backend/src/schema/resolvers/embeds.ts index a4368a42f..8ce144b4f 100644 --- a/backend/src/schema/resolvers/embeds.ts +++ b/backend/src/schema/resolvers/embeds.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ import scrape from './embeds/scraper' import { undefinedToNullResolver } from './helpers/Resolver' diff --git a/backend/src/schema/resolvers/embeds/findProvider.ts b/backend/src/schema/resolvers/embeds/findProvider.ts index a9a30f2bf..9be9732bf 100644 --- a/backend/src/schema/resolvers/embeds/findProvider.ts +++ b/backend/src/schema/resolvers/embeds/findProvider.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import fs from 'node:fs' import path from 'node:path' diff --git a/backend/src/schema/resolvers/embeds/scraper.ts b/backend/src/schema/resolvers/embeds/scraper.ts index 1942917bf..bcd25046b 100644 --- a/backend/src/schema/resolvers/embeds/scraper.ts +++ b/backend/src/schema/resolvers/embeds/scraper.ts @@ -1,3 +1,9 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable n/no-extraneous-require */ /* eslint-disable n/global-require */ diff --git a/backend/src/schema/resolvers/filter-posts.spec.ts b/backend/src/schema/resolvers/filter-posts.spec.ts index d5d4485a3..83421bc37 100644 --- a/backend/src/schema/resolvers/filter-posts.spec.ts +++ b/backend/src/schema/resolvers/filter-posts.spec.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import CONFIG from '@config/index' diff --git a/backend/src/schema/resolvers/follow.spec.ts b/backend/src/schema/resolvers/follow.spec.ts index 1e05b2fea..437b4e160 100644 --- a/backend/src/schema/resolvers/follow.spec.ts +++ b/backend/src/schema/resolvers/follow.spec.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/follow.ts b/backend/src/schema/resolvers/follow.ts index 11447974d..d3c1a9081 100644 --- a/backend/src/schema/resolvers/follow.ts +++ b/backend/src/schema/resolvers/follow.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { getNeode } from '@db/neo4j' const neode = getNeode() diff --git a/backend/src/schema/resolvers/groups.spec.ts b/backend/src/schema/resolvers/groups.spec.ts index d9d060fea..a61d7e8b9 100644 --- a/backend/src/schema/resolvers/groups.spec.ts +++ b/backend/src/schema/resolvers/groups.spec.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { createTestClient } from 'apollo-server-testing' diff --git a/backend/src/schema/resolvers/groups.ts b/backend/src/schema/resolvers/groups.ts index f1befc67b..96d806bf8 100644 --- a/backend/src/schema/resolvers/groups.ts +++ b/backend/src/schema/resolvers/groups.ts @@ -1,3 +1,10 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { UserInputError } from 'apollo-server' import { v4 as uuid } from 'uuid' diff --git a/backend/src/schema/resolvers/helpers/Resolver.ts b/backend/src/schema/resolvers/helpers/Resolver.ts index 8119614df..cad886ace 100644 --- a/backend/src/schema/resolvers/helpers/Resolver.ts +++ b/backend/src/schema/resolvers/helpers/Resolver.ts @@ -1,3 +1,10 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable security/detect-object-injection */ import log from './databaseLogger' diff --git a/backend/src/schema/resolvers/helpers/createPasswordReset.ts b/backend/src/schema/resolvers/helpers/createPasswordReset.ts index ec0349c18..0727c5d4e 100644 --- a/backend/src/schema/resolvers/helpers/createPasswordReset.ts +++ b/backend/src/schema/resolvers/helpers/createPasswordReset.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import normalizeEmail from './normalizeEmail' export default async function createPasswordReset(options) { diff --git a/backend/src/schema/resolvers/helpers/databaseLogger.ts b/backend/src/schema/resolvers/helpers/databaseLogger.ts index f2db22965..ad1de5828 100644 --- a/backend/src/schema/resolvers/helpers/databaseLogger.ts +++ b/backend/src/schema/resolvers/helpers/databaseLogger.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable import/no-named-as-default */ // eslint-disable-next-line import/no-extraneous-dependencies import Debug from 'debug' diff --git a/backend/src/schema/resolvers/helpers/events.ts b/backend/src/schema/resolvers/helpers/events.ts index d4fc1fb11..ba68fb587 100644 --- a/backend/src/schema/resolvers/helpers/events.ts +++ b/backend/src/schema/resolvers/helpers/events.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { UserInputError } from 'apollo-server' export const validateEventParams = (params) => { diff --git a/backend/src/schema/resolvers/helpers/existingEmailAddress.ts b/backend/src/schema/resolvers/helpers/existingEmailAddress.ts index 288a14a6d..e1e27bda0 100644 --- a/backend/src/schema/resolvers/helpers/existingEmailAddress.ts +++ b/backend/src/schema/resolvers/helpers/existingEmailAddress.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ export default async function alreadyExistingMail({ args, context }) { const session = context.driver.session() try { diff --git a/backend/src/schema/resolvers/helpers/filterForMutedUsers.ts b/backend/src/schema/resolvers/helpers/filterForMutedUsers.ts index 5a53bf9cb..88d66dd65 100644 --- a/backend/src/schema/resolvers/helpers/filterForMutedUsers.ts +++ b/backend/src/schema/resolvers/helpers/filterForMutedUsers.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { mergeWith, isArray } from 'lodash' import { getMutedUsers } from '@schema/resolvers/users' diff --git a/backend/src/schema/resolvers/helpers/filterInvisiblePosts.ts b/backend/src/schema/resolvers/helpers/filterInvisiblePosts.ts index 73dfaad91..7769ac5f9 100644 --- a/backend/src/schema/resolvers/helpers/filterInvisiblePosts.ts +++ b/backend/src/schema/resolvers/helpers/filterInvisiblePosts.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { mergeWith, isArray } from 'lodash' const getInvisiblePosts = async (context) => { diff --git a/backend/src/schema/resolvers/helpers/filterPostsOfMyGroups.ts b/backend/src/schema/resolvers/helpers/filterPostsOfMyGroups.ts index a808a5582..255d32c04 100644 --- a/backend/src/schema/resolvers/helpers/filterPostsOfMyGroups.ts +++ b/backend/src/schema/resolvers/helpers/filterPostsOfMyGroups.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { mergeWith, isArray } from 'lodash' const getMyGroupIds = async (context) => { diff --git a/backend/src/schema/resolvers/helpers/normalizeEmail.ts b/backend/src/schema/resolvers/helpers/normalizeEmail.ts index bc13467c3..9b6be73d7 100644 --- a/backend/src/schema/resolvers/helpers/normalizeEmail.ts +++ b/backend/src/schema/resolvers/helpers/normalizeEmail.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ import { normalizeEmail } from 'validator' export default (email) => diff --git a/backend/src/schema/resolvers/images/images.spec.ts b/backend/src/schema/resolvers/images/images.spec.ts index a4eb5b1a5..8f8cebf36 100644 --- a/backend/src/schema/resolvers/images/images.spec.ts +++ b/backend/src/schema/resolvers/images/images.spec.ts @@ -1,3 +1,10 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/await-thenable */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable promise/prefer-await-to-callbacks */ import { UserInputError } from 'apollo-server' diff --git a/backend/src/schema/resolvers/images/images.ts b/backend/src/schema/resolvers/images/images.ts index b7d6f3110..d8ce03758 100644 --- a/backend/src/schema/resolvers/images/images.ts +++ b/backend/src/schema/resolvers/images/images.ts @@ -1,3 +1,10 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable promise/avoid-new */ /* eslint-disable security/detect-non-literal-fs-filename */ import { existsSync, unlinkSync, createWriteStream } from 'node:fs' diff --git a/backend/src/schema/resolvers/inviteCodes.spec.ts b/backend/src/schema/resolvers/inviteCodes.spec.ts index e1a0dac17..aac79210f 100644 --- a/backend/src/schema/resolvers/inviteCodes.spec.ts +++ b/backend/src/schema/resolvers/inviteCodes.spec.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable security/detect-non-literal-regexp */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/inviteCodes.ts b/backend/src/schema/resolvers/inviteCodes.ts index 442ff17b1..02680b5bc 100644 --- a/backend/src/schema/resolvers/inviteCodes.ts +++ b/backend/src/schema/resolvers/inviteCodes.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ import generateInviteCode from './helpers/generateInviteCode' import Resolver from './helpers/Resolver' import { validateInviteCode } from './transactions/inviteCodes' diff --git a/backend/src/schema/resolvers/locations.spec.ts b/backend/src/schema/resolvers/locations.spec.ts index 824372d28..d4510284c 100644 --- a/backend/src/schema/resolvers/locations.spec.ts +++ b/backend/src/schema/resolvers/locations.spec.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/locations.ts b/backend/src/schema/resolvers/locations.ts index 5c2f67e20..bcefa2337 100644 --- a/backend/src/schema/resolvers/locations.ts +++ b/backend/src/schema/resolvers/locations.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ import { UserInputError } from 'apollo-server' import Resolver from './helpers/Resolver' diff --git a/backend/src/schema/resolvers/messages.spec.ts b/backend/src/schema/resolvers/messages.spec.ts index 83d4134cb..c4aac03f8 100644 --- a/backend/src/schema/resolvers/messages.spec.ts +++ b/backend/src/schema/resolvers/messages.spec.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/await-thenable */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import Factory, { cleanDatabase } from '@db/factories' diff --git a/backend/src/schema/resolvers/messages.ts b/backend/src/schema/resolvers/messages.ts index 1d59b4bbd..c3f362660 100644 --- a/backend/src/schema/resolvers/messages.ts +++ b/backend/src/schema/resolvers/messages.ts @@ -1,3 +1,9 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ import { withFilter } from 'graphql-subscriptions' import { neo4jgraphql } from 'neo4j-graphql-js' diff --git a/backend/src/schema/resolvers/moderation.spec.ts b/backend/src/schema/resolvers/moderation.spec.ts index 46befdf10..67d070893 100644 --- a/backend/src/schema/resolvers/moderation.spec.ts +++ b/backend/src/schema/resolvers/moderation.spec.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/moderation.ts b/backend/src/schema/resolvers/moderation.ts index a29a411aa..6fe8637c6 100644 --- a/backend/src/schema/resolvers/moderation.ts +++ b/backend/src/schema/resolvers/moderation.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import log from './helpers/databaseLogger' export default { diff --git a/backend/src/schema/resolvers/notifications.spec.ts b/backend/src/schema/resolvers/notifications.spec.ts index a10f97590..7678a547d 100644 --- a/backend/src/schema/resolvers/notifications.spec.ts +++ b/backend/src/schema/resolvers/notifications.spec.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/await-thenable */ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/notifications.ts b/backend/src/schema/resolvers/notifications.ts index c93655ae3..6151d305e 100644 --- a/backend/src/schema/resolvers/notifications.ts +++ b/backend/src/schema/resolvers/notifications.ts @@ -1,3 +1,9 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { withFilter } from 'graphql-subscriptions' import { pubsub, NOTIFICATION_ADDED } from '@src/server' diff --git a/backend/src/schema/resolvers/observePosts.spec.ts b/backend/src/schema/resolvers/observePosts.spec.ts index 13fd5ccfc..bf58ef5ab 100644 --- a/backend/src/schema/resolvers/observePosts.spec.ts +++ b/backend/src/schema/resolvers/observePosts.spec.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/passwordReset.spec.ts b/backend/src/schema/resolvers/passwordReset.spec.ts index e88a1e503..7a260d345 100644 --- a/backend/src/schema/resolvers/passwordReset.spec.ts +++ b/backend/src/schema/resolvers/passwordReset.spec.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/passwordReset.ts b/backend/src/schema/resolvers/passwordReset.ts index b9d4d7f51..9995db969 100644 --- a/backend/src/schema/resolvers/passwordReset.ts +++ b/backend/src/schema/resolvers/passwordReset.ts @@ -1,3 +1,9 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/await-thenable */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import bcrypt from 'bcryptjs' import { v4 as uuid } from 'uuid' diff --git a/backend/src/schema/resolvers/posts.spec.ts b/backend/src/schema/resolvers/posts.spec.ts index 103ba98c0..9c4f5ebad 100644 --- a/backend/src/schema/resolvers/posts.spec.ts +++ b/backend/src/schema/resolvers/posts.spec.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/posts.ts b/backend/src/schema/resolvers/posts.ts index c7d3a1b14..f981662ba 100644 --- a/backend/src/schema/resolvers/posts.ts +++ b/backend/src/schema/resolvers/posts.ts @@ -1,3 +1,9 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { UserInputError } from 'apollo-server' import { isEmpty } from 'lodash' import { neo4jgraphql } from 'neo4j-graphql-js' diff --git a/backend/src/schema/resolvers/postsInGroups.spec.ts b/backend/src/schema/resolvers/postsInGroups.spec.ts index 17d4f274e..ca4d45f96 100644 --- a/backend/src/schema/resolvers/postsInGroups.spec.ts +++ b/backend/src/schema/resolvers/postsInGroups.spec.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import CONFIG from '@config/index' diff --git a/backend/src/schema/resolvers/registration.spec.ts b/backend/src/schema/resolvers/registration.spec.ts index e61460786..9a0e578cd 100644 --- a/backend/src/schema/resolvers/registration.spec.ts +++ b/backend/src/schema/resolvers/registration.spec.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/registration.ts b/backend/src/schema/resolvers/registration.ts index fc3fc37bb..138a21aea 100644 --- a/backend/src/schema/resolvers/registration.ts +++ b/backend/src/schema/resolvers/registration.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { UserInputError } from 'apollo-server' import { getNeode } from '@db/neo4j' diff --git a/backend/src/schema/resolvers/reports.spec.ts b/backend/src/schema/resolvers/reports.spec.ts index a57efc011..4401329cb 100644 --- a/backend/src/schema/resolvers/reports.spec.ts +++ b/backend/src/schema/resolvers/reports.spec.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/reports.ts b/backend/src/schema/resolvers/reports.ts index f7945e060..35e250f48 100644 --- a/backend/src/schema/resolvers/reports.ts +++ b/backend/src/schema/resolvers/reports.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import log from './helpers/databaseLogger' export default { diff --git a/backend/src/schema/resolvers/roles.ts b/backend/src/schema/resolvers/roles.ts index fba57c35a..006d4f5ba 100644 --- a/backend/src/schema/resolvers/roles.ts +++ b/backend/src/schema/resolvers/roles.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/require-await */ export default { Query: { availableRoles: async (_parent, _args, _context, _resolveInfo) => { diff --git a/backend/src/schema/resolvers/rooms.spec.ts b/backend/src/schema/resolvers/rooms.spec.ts index 25fb72214..81c75cda5 100644 --- a/backend/src/schema/resolvers/rooms.spec.ts +++ b/backend/src/schema/resolvers/rooms.spec.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import Factory, { cleanDatabase } from '@db/factories' diff --git a/backend/src/schema/resolvers/rooms.ts b/backend/src/schema/resolvers/rooms.ts index 7b87e9007..9c6751695 100644 --- a/backend/src/schema/resolvers/rooms.ts +++ b/backend/src/schema/resolvers/rooms.ts @@ -1,3 +1,9 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { withFilter } from 'graphql-subscriptions' import { neo4jgraphql } from 'neo4j-graphql-js' diff --git a/backend/src/schema/resolvers/searches.spec.ts b/backend/src/schema/resolvers/searches.spec.ts index 5902f2746..cb774cad5 100644 --- a/backend/src/schema/resolvers/searches.spec.ts +++ b/backend/src/schema/resolvers/searches.spec.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/searches.ts b/backend/src/schema/resolvers/searches.ts index 711166d5e..845a070a5 100644 --- a/backend/src/schema/resolvers/searches.ts +++ b/backend/src/schema/resolvers/searches.ts @@ -1,3 +1,9 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ import log from './helpers/databaseLogger' import { queryString } from './searches/queryString' diff --git a/backend/src/schema/resolvers/searches/queryString.ts b/backend/src/schema/resolvers/searches/queryString.ts index 8f415c5e6..da8e7bffb 100644 --- a/backend/src/schema/resolvers/searches/queryString.ts +++ b/backend/src/schema/resolvers/searches/queryString.ts @@ -1,3 +1,9 @@ +/* eslint-disable @typescript-eslint/restrict-plus-operands */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ export function queryString(str) { const normalizedString = normalizeWhitespace(str) const escapedString = escapeSpecialCharacters(normalizedString) diff --git a/backend/src/schema/resolvers/shout.spec.ts b/backend/src/schema/resolvers/shout.spec.ts index 7fe7176ab..4ec6189bd 100644 --- a/backend/src/schema/resolvers/shout.spec.ts +++ b/backend/src/schema/resolvers/shout.spec.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/shout.ts b/backend/src/schema/resolvers/shout.ts index 8c330cd67..0a7ec6a39 100644 --- a/backend/src/schema/resolvers/shout.ts +++ b/backend/src/schema/resolvers/shout.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import log from './helpers/databaseLogger' export default { diff --git a/backend/src/schema/resolvers/socialMedia.spec.ts b/backend/src/schema/resolvers/socialMedia.spec.ts index 3a36e791e..584e64cfb 100644 --- a/backend/src/schema/resolvers/socialMedia.spec.ts +++ b/backend/src/schema/resolvers/socialMedia.spec.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/socialMedia.ts b/backend/src/schema/resolvers/socialMedia.ts index 53877e431..d3a563d2c 100644 --- a/backend/src/schema/resolvers/socialMedia.ts +++ b/backend/src/schema/resolvers/socialMedia.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { getNeode } from '@db/neo4j' import Resolver from './helpers/Resolver' diff --git a/backend/src/schema/resolvers/statistics.spec.ts b/backend/src/schema/resolvers/statistics.spec.ts index 4c8c8aa01..9d68b611f 100644 --- a/backend/src/schema/resolvers/statistics.spec.ts +++ b/backend/src/schema/resolvers/statistics.spec.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/statistics.ts b/backend/src/schema/resolvers/statistics.ts index 6bdd7ac5e..e2b93bbea 100644 --- a/backend/src/schema/resolvers/statistics.ts +++ b/backend/src/schema/resolvers/statistics.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable security/detect-object-injection */ import log from './helpers/databaseLogger' diff --git a/backend/src/schema/resolvers/transactions/inviteCodes.ts b/backend/src/schema/resolvers/transactions/inviteCodes.ts index 554b15f86..0381893ad 100644 --- a/backend/src/schema/resolvers/transactions/inviteCodes.ts +++ b/backend/src/schema/resolvers/transactions/inviteCodes.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ export async function validateInviteCode(session, inviteCode) { const readTxResultPromise = session.readTransaction(async (txc) => { const result = await txc.run( diff --git a/backend/src/schema/resolvers/userData.spec.ts b/backend/src/schema/resolvers/userData.spec.ts index 1165ec33c..b3cd75694 100644 --- a/backend/src/schema/resolvers/userData.spec.ts +++ b/backend/src/schema/resolvers/userData.spec.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/userData.ts b/backend/src/schema/resolvers/userData.ts index 5c892b7ab..15c65b59b 100644 --- a/backend/src/schema/resolvers/userData.ts +++ b/backend/src/schema/resolvers/userData.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ export default { Query: { userData: async (_object, _args, context, _resolveInfo) => { diff --git a/backend/src/schema/resolvers/user_management.spec.ts b/backend/src/schema/resolvers/user_management.spec.ts index 527821856..f76a1f9fb 100644 --- a/backend/src/schema/resolvers/user_management.spec.ts +++ b/backend/src/schema/resolvers/user_management.spec.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable promise/prefer-await-to-callbacks */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/user_management.ts b/backend/src/schema/resolvers/user_management.ts index 70b4f6520..77f26ceb4 100644 --- a/backend/src/schema/resolvers/user_management.ts +++ b/backend/src/schema/resolvers/user_management.ts @@ -1,3 +1,10 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/await-thenable */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { AuthenticationError } from 'apollo-server' import bcrypt from 'bcryptjs' import { neo4jgraphql } from 'neo4j-graphql-js' diff --git a/backend/src/schema/resolvers/users.spec.ts b/backend/src/schema/resolvers/users.spec.ts index 486d7e033..0a74d46d3 100644 --- a/backend/src/schema/resolvers/users.spec.ts +++ b/backend/src/schema/resolvers/users.spec.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/users.ts b/backend/src/schema/resolvers/users.ts index 578726dc6..0abee49fd 100644 --- a/backend/src/schema/resolvers/users.ts +++ b/backend/src/schema/resolvers/users.ts @@ -1,3 +1,10 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { UserInputError, ForbiddenError } from 'apollo-server' import { neo4jgraphql } from 'neo4j-graphql-js' diff --git a/backend/src/schema/resolvers/users/location.spec.ts b/backend/src/schema/resolvers/users/location.spec.ts index 4f54dcc06..9c3791e35 100644 --- a/backend/src/schema/resolvers/users/location.spec.ts +++ b/backend/src/schema/resolvers/users/location.spec.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/users/location.ts b/backend/src/schema/resolvers/users/location.ts index e5ab85d75..2a4ce6427 100644 --- a/backend/src/schema/resolvers/users/location.ts +++ b/backend/src/schema/resolvers/users/location.ts @@ -1,3 +1,10 @@ +/* eslint-disable @typescript-eslint/restrict-plus-operands */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable promise/avoid-new */ /* eslint-disable promise/prefer-await-to-callbacks */ diff --git a/backend/src/schema/resolvers/users/mutedUsers.spec.ts b/backend/src/schema/resolvers/users/mutedUsers.spec.ts index 93c54625d..455672199 100644 --- a/backend/src/schema/resolvers/users/mutedUsers.spec.ts +++ b/backend/src/schema/resolvers/users/mutedUsers.spec.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/schema/resolvers/viewedTeaserCount.spec.ts b/backend/src/schema/resolvers/viewedTeaserCount.spec.ts index ebcb19c4e..9fb8a7eb9 100644 --- a/backend/src/schema/resolvers/viewedTeaserCount.spec.ts +++ b/backend/src/schema/resolvers/viewedTeaserCount.spec.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/server.ts b/backend/src/server.ts index 833313253..372ec964b 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -1,3 +1,8 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable import/no-named-as-default-member */ import http from 'node:http' From 5131752710d4653a4a907bcc7c0f48694138b963 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Mon, 21 Apr 2025 13:09:47 +0200 Subject: [PATCH 18/54] refactor(backend): lint @typescript-eslint/strict (#8408) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * lint @typescript-eslint/recommended * lint @typescript-eslint/recommended-requiring-type-checking fix type not detected locally due to wierd uuid typings missing save error not reported locally * lint @typescript-eslint/strict --------- Co-authored-by: Wolfgang Huß --- backend/.eslintrc.cjs | 2 +- .../notifications/notificationsMiddleware.ts | 4 ++-- .../src/middleware/permissionsMiddleware.ts | 18 +++++++++--------- backend/src/middleware/sentryMiddleware.ts | 2 +- .../validation/validationMiddleware.ts | 2 +- backend/src/schema/resolvers/emails.ts | 2 +- backend/src/schema/resolvers/groups.spec.ts | 2 +- .../src/schema/resolvers/helpers/Resolver.ts | 1 + backend/src/schema/resolvers/helpers/events.ts | 2 +- .../resolvers/helpers/filterInvisiblePosts.ts | 2 +- .../resolvers/helpers/filterPostsOfMyGroups.ts | 4 ++-- backend/src/schema/resolvers/passwordReset.ts | 2 +- .../src/schema/resolvers/user_management.ts | 2 +- backend/src/schema/resolvers/users.ts | 2 +- backend/src/schema/resolvers/users/location.ts | 10 +++++----- 15 files changed, 29 insertions(+), 28 deletions(-) diff --git a/backend/.eslintrc.cjs b/backend/.eslintrc.cjs index 684c26948..51adb6831 100644 --- a/backend/.eslintrc.cjs +++ b/backend/.eslintrc.cjs @@ -181,7 +181,7 @@ module.exports = { extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended-requiring-type-checking', - // 'plugin:@typescript-eslint/strict', + 'plugin:@typescript-eslint/strict', 'prettier', ], rules: { diff --git a/backend/src/middleware/notifications/notificationsMiddleware.ts b/backend/src/middleware/notifications/notificationsMiddleware.ts index 5d2febffd..f7be031c8 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.ts @@ -19,7 +19,7 @@ import { pubsub, NOTIFICATION_ADDED, ROOM_COUNT_UPDATED, CHAT_MESSAGE_ADDED } fr import extractMentionedUsers from './mentions/extractMentionedUsers' const queryNotificationEmails = async (context, notificationUserIds) => { - if (!(notificationUserIds && notificationUserIds.length)) return [] + if (!notificationUserIds?.length) return [] const userEmailCypher = ` MATCH (user: User) // blocked users are filtered out from notifications already @@ -360,7 +360,7 @@ const notifyMemberOfGroup = async (groupId, userId, reason, context) => { } const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => { - if (!(idsOfUsers && idsOfUsers.length)) return [] + if (!idsOfUsers?.length) return [] await validateNotifyUsers(label, reason) let mentionedCypher switch (reason) { diff --git a/backend/src/middleware/permissionsMiddleware.ts b/backend/src/middleware/permissionsMiddleware.ts index a17072c4b..20063de11 100644 --- a/backend/src/middleware/permissionsMiddleware.ts +++ b/backend/src/middleware/permissionsMiddleware.ts @@ -18,7 +18,7 @@ const neode = getNeode() const isAuthenticated = rule({ cache: 'contextual', })(async (_parent, _args, ctx, _info) => { - return !!(ctx && ctx.user && ctx.user.id) + return !!ctx?.user?.id }) const isModerator = rule()(async (_parent, _args, { user }, _info) => { @@ -62,7 +62,7 @@ const isMySocialMedia = rule({ const isAllowedToChangeGroupSettings = rule({ cache: 'no_cache', })(async (_parent, args, { user, driver }) => { - if (!(user && user.id)) return false + if (!user?.id) return false const ownerId = user.id const { id: groupId } = args const session = driver.session() @@ -92,7 +92,7 @@ const isAllowedToChangeGroupSettings = rule({ const isAllowedSeeingGroupMembers = rule({ cache: 'no_cache', })(async (_parent, args, { user, driver }) => { - if (!(user && user.id)) return false + if (!user?.id) return false const { id: groupId } = args const session = driver.session() const readTxPromise = session.readTransaction(async (transaction) => { @@ -128,7 +128,7 @@ const isAllowedSeeingGroupMembers = rule({ const isAllowedToChangeGroupMemberRole = rule({ cache: 'no_cache', })(async (_parent, args, { user, driver }) => { - if (!(user && user.id)) return false + if (!user?.id) return false const currentUserId = user.id const { groupId, userId, roleInGroup } = args if (currentUserId === userId) return false @@ -175,7 +175,7 @@ const isAllowedToChangeGroupMemberRole = rule({ const isAllowedToJoinGroup = rule({ cache: 'no_cache', })(async (_parent, args, { user, driver }) => { - if (!(user && user.id)) return false + if (!user?.id) return false const { groupId, userId } = args const session = driver.session() const readTxPromise = session.readTransaction(async (transaction) => { @@ -205,7 +205,7 @@ const isAllowedToJoinGroup = rule({ const isAllowedToLeaveGroup = rule({ cache: 'no_cache', })(async (_parent, args, { user, driver }) => { - if (!(user && user.id)) return false + if (!user?.id) return false const { groupId, userId } = args if (user.id !== userId) return false const session = driver.session() @@ -235,7 +235,7 @@ const isAllowedToLeaveGroup = rule({ const isMemberOfGroup = rule({ cache: 'no_cache', })(async (_parent, args, { user, driver }) => { - if (!(user && user.id)) return false + if (!user?.id) return false const { groupId } = args if (!groupId) return true const userId = user.id @@ -263,7 +263,7 @@ const isMemberOfGroup = rule({ const canRemoveUserFromGroup = rule({ cache: 'no_cache', })(async (_parent, args, { user, driver }) => { - if (!(user && user.id)) return false + if (!user?.id) return false const { groupId, userId } = args const currentUserId = user.id if (currentUserId === userId) return false @@ -299,7 +299,7 @@ const canRemoveUserFromGroup = rule({ const canCommentPost = rule({ cache: 'no_cache', })(async (_parent, args, { user, driver }) => { - if (!(user && user.id)) return false + if (!user?.id) return false const { postId } = args const userId = user.id const session = driver.session() diff --git a/backend/src/middleware/sentryMiddleware.ts b/backend/src/middleware/sentryMiddleware.ts index 6577be4ee..743ec32df 100644 --- a/backend/src/middleware/sentryMiddleware.ts +++ b/backend/src/middleware/sentryMiddleware.ts @@ -21,7 +21,7 @@ if (CONFIG.SENTRY_DSN_BACKEND) { // eslint-disable-next-line @typescript-eslint/no-explicit-any withScope: (scope, error, context: any) => { scope.setUser({ - id: context.user && context.user.id, + id: context.user?.id, }) scope.setExtra('body', context.req.body) scope.setExtra('origin', context.req.headers.origin) diff --git a/backend/src/middleware/validation/validationMiddleware.ts b/backend/src/middleware/validation/validationMiddleware.ts index 317595ee3..75f8f5d09 100644 --- a/backend/src/middleware/validation/validationMiddleware.ts +++ b/backend/src/middleware/validation/validationMiddleware.ts @@ -87,7 +87,7 @@ const validateReview = async (resolve, root, args, context, info) => { try { const txResult = await reportReadTxPromise existingReportedResource = txResult - if (!existingReportedResource || !existingReportedResource.length) + if (!existingReportedResource?.length) throw new Error(`Resource not found or is not a Post|Comment|User!`) existingReportedResource = existingReportedResource[0] if (!existingReportedResource.filed) diff --git a/backend/src/schema/resolvers/emails.ts b/backend/src/schema/resolvers/emails.ts index d4313558e..be721dda5 100644 --- a/backend/src/schema/resolvers/emails.ts +++ b/backend/src/schema/resolvers/emails.ts @@ -48,7 +48,7 @@ export default { // check email does not belong to anybody const existingEmail = await existingEmailAddress({ args, context }) - if (existingEmail && existingEmail.alreadyExistingEmail && existingEmail.user) + if (existingEmail?.alreadyExistingEmail && existingEmail.user) return existingEmail.alreadyExistingEmail const nonce = generateNonce() diff --git a/backend/src/schema/resolvers/groups.spec.ts b/backend/src/schema/resolvers/groups.spec.ts index a61d7e8b9..c8ce48c37 100644 --- a/backend/src/schema/resolvers/groups.spec.ts +++ b/backend/src/schema/resolvers/groups.spec.ts @@ -2429,7 +2429,7 @@ describe('in mode', () => { id: groupId, }, }) - return result.data && result.data.GroupMembers + return result.data?.GroupMembers ? !!result.data.GroupMembers.find((member) => member.id === userId) : null } diff --git a/backend/src/schema/resolvers/helpers/Resolver.ts b/backend/src/schema/resolvers/helpers/Resolver.ts index cad886ace..2d7faa7b7 100644 --- a/backend/src/schema/resolvers/helpers/Resolver.ts +++ b/backend/src/schema/resolvers/helpers/Resolver.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-dynamic-delete */ /* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ diff --git a/backend/src/schema/resolvers/helpers/events.ts b/backend/src/schema/resolvers/helpers/events.ts index ba68fb587..3e5f8d5a8 100644 --- a/backend/src/schema/resolvers/helpers/events.ts +++ b/backend/src/schema/resolvers/helpers/events.ts @@ -22,7 +22,7 @@ export const validateEventParams = (params) => { throw new UserInputError('Event venue must be present if event location is given!') } params.eventVenue = eventInput.eventVenue - params.eventLocationName = eventInput.eventLocationName && eventInput.eventLocationName.trim() + params.eventLocationName = eventInput.eventLocationName?.trim() if (params.eventLocationName) { locationName = params.eventLocationName } else { diff --git a/backend/src/schema/resolvers/helpers/filterInvisiblePosts.ts b/backend/src/schema/resolvers/helpers/filterInvisiblePosts.ts index 7769ac5f9..2a264ced4 100644 --- a/backend/src/schema/resolvers/helpers/filterInvisiblePosts.ts +++ b/backend/src/schema/resolvers/helpers/filterInvisiblePosts.ts @@ -9,7 +9,7 @@ const getInvisiblePosts = async (context) => { const readTxResultPromise = await session.readTransaction(async (transaction) => { let cypher = '' const { user } = context - if (user && user.id) { + if (user?.id) { cypher = ` MATCH (post:Post)<-[:CANNOT_SEE]-(user:User { id: $userId }) RETURN collect(post.id) AS invisiblePostIds` diff --git a/backend/src/schema/resolvers/helpers/filterPostsOfMyGroups.ts b/backend/src/schema/resolvers/helpers/filterPostsOfMyGroups.ts index 255d32c04..9d40b097e 100644 --- a/backend/src/schema/resolvers/helpers/filterPostsOfMyGroups.ts +++ b/backend/src/schema/resolvers/helpers/filterPostsOfMyGroups.ts @@ -6,7 +6,7 @@ import { mergeWith, isArray } from 'lodash' const getMyGroupIds = async (context) => { const { user } = context - if (!(user && user.id)) return [] + if (!user?.id) return [] const session = context.driver.session() const readTxResultPromise = await session.readTransaction(async (transaction) => { @@ -26,7 +26,7 @@ const getMyGroupIds = async (context) => { } export const filterPostsOfMyGroups = async (params, context) => { - if (!(params.filter && params.filter.postsInMyGroups)) return params + if (!params.filter?.postsInMyGroups) return params delete params.filter.postsInMyGroups const myGroupIds = await getMyGroupIds(context) params.filter = mergeWith( diff --git a/backend/src/schema/resolvers/passwordReset.ts b/backend/src/schema/resolvers/passwordReset.ts index 9995db969..ab53d65fa 100644 --- a/backend/src/schema/resolvers/passwordReset.ts +++ b/backend/src/schema/resolvers/passwordReset.ts @@ -47,7 +47,7 @@ export default { ) }) const [reset] = await passwordResetTxPromise - return !!(reset && reset.properties.usedAt) + return !!reset?.properties.usedAt } finally { session.close() } diff --git a/backend/src/schema/resolvers/user_management.ts b/backend/src/schema/resolvers/user_management.ts index 77f26ceb4..13437e815 100644 --- a/backend/src/schema/resolvers/user_management.ts +++ b/backend/src/schema/resolvers/user_management.ts @@ -49,7 +49,7 @@ export default { ) { delete currentUser.encryptedPassword return encode(currentUser) - } else if (currentUser && currentUser.disabled) { + } else if (currentUser?.disabled) { throw new AuthenticationError('Your account has been disabled.') } else { throw new AuthenticationError('Incorrect email address or password.') diff --git a/backend/src/schema/resolvers/users.ts b/backend/src/schema/resolvers/users.ts index 0abee49fd..4f1fb6d5b 100644 --- a/backend/src/schema/resolvers/users.ts +++ b/backend/src/schema/resolvers/users.ts @@ -228,7 +228,7 @@ export default { const session = context.driver.session() const deleteUserTxResultPromise = session.writeTransaction(async (transaction) => { - if (resource && resource.length) { + if (resource?.length) { await Promise.all( resource.map(async (node) => { const txResult = await transaction.run( diff --git a/backend/src/schema/resolvers/users/location.ts b/backend/src/schema/resolvers/users/location.ts index 2a4ce6427..d32c03cd2 100644 --- a/backend/src/schema/resolvers/users/location.ts +++ b/backend/src/schema/resolvers/users/location.ts @@ -47,8 +47,8 @@ const createLocation = async (session, mapboxData) => { nameRU: mapboxData.text_ru, type: mapboxData.id.split('.')[0].toLowerCase(), address: mapboxData.address, - lng: mapboxData.center && mapboxData.center.length ? mapboxData.center[0] : null, - lat: mapboxData.center && mapboxData.center.length ? mapboxData.center[1] : null, + lng: mapboxData.center?.length ? mapboxData.center[0] : null, + lat: mapboxData.center?.length ? mapboxData.center[1] : null, } let mutation = @@ -95,7 +95,7 @@ export const createOrUpdateLocations = async (nodeLabel, nodeId, locationName, s debug(res) - if (!res || !res.features || !res.features[0]) { + if (!res?.features?.[0]) { throw new UserInputError('locationName is invalid') } @@ -110,7 +110,7 @@ export const createOrUpdateLocations = async (nodeLabel, nodeId, locationName, s data = res.features[0] } - if (!data || !data.place_type || !data.place_type.length) { + if (!data?.place_type?.length) { throw new UserInputError('locationName is invalid') } @@ -172,7 +172,7 @@ export const queryLocations = async ({ place, lang }) => { `https://api.mapbox.com/geocoding/v5/mapbox.places/${place}.json?access_token=${CONFIG.MAPBOX_TOKEN}&types=region,place,country&language=${lang}`, ) // Return empty array if no location found or error occurred - if (!res || !res.features) { + if (!res?.features) { return [] } return res.features From 6f4d347f69c8b134dde035d118da009542e7aeb0 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Mon, 21 Apr 2025 13:47:12 +0200 Subject: [PATCH 19/54] refactor(backend): separate queries (#8358) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * separate all queries into one file each * fix merge error * fix lint --------- Co-authored-by: mahula Co-authored-by: Wolfgang Huß --- backend/src/db/seed.ts | 16 +- backend/src/graphql/groups.ts | 216 ------------------ backend/src/graphql/messages.ts | 50 ---- backend/src/graphql/notifications.ts | 65 ------ backend/src/graphql/posts.ts | 113 --------- .../queries/changeGroupMemberRoleMutation.ts | 14 ++ .../createCommentMutation.ts} | 6 - .../graphql/queries/createGroupMutation.ts | 55 +++++ .../graphql/queries/createMessageMutation.ts | 19 ++ .../src/graphql/queries/createPostMutation.ts | 52 +++++ .../src/graphql/queries/createRoomMutation.ts | 24 ++ backend/src/graphql/queries/filterPosts.ts | 14 ++ .../src/graphql/queries/groupMembersQuery.ts | 14 ++ backend/src/graphql/queries/groupQuery.ts | 38 +++ .../src/graphql/queries/joinGroupMutation.ts | 14 ++ .../src/graphql/queries/leaveGroupMutation.ts | 14 ++ .../loginMutation.ts} | 6 - .../graphql/queries/markAllAsReadMutation.ts | 21 ++ .../src/graphql/queries/markAsReadMutation.ts | 21 ++ .../src/graphql/queries/markMessagesAsSeen.ts | 9 + backend/src/graphql/queries/messageQuery.ts | 24 ++ .../src/graphql/queries/notificationQuery.ts | 21 ++ backend/src/graphql/queries/postQuery.ts | 13 ++ .../src/graphql/queries/profilePagePosts.ts | 18 ++ .../queries/removeUserFromGroupMutation.ts | 14 ++ .../{rooms.ts => queries/roomQuery.ts} | 31 --- backend/src/graphql/queries/searchPosts.ts | 16 ++ .../signupVerificationMutation.ts} | 6 - .../src/graphql/queries/unreadRoomsQuery.ts | 9 + .../graphql/queries/updateGroupMutation.ts | 56 +++++ .../notificationsMiddleware.emails.spec.ts | 3 +- ...ficationsMiddleware.followed-users.spec.ts | 2 +- ...tionsMiddleware.mentions-in-groups.spec.ts | 8 +- ...icationsMiddleware.posts-in-groups.spec.ts | 8 +- .../notificationsMiddleware.spec.ts | 16 +- .../src/middleware/slugifyMiddleware.spec.ts | 7 +- .../src/schema/resolvers/filter-posts.spec.ts | 3 +- backend/src/schema/resolvers/groups.spec.ts | 18 +- backend/src/schema/resolvers/messages.spec.ts | 7 +- .../schema/resolvers/notifications.spec.ts | 8 +- .../src/schema/resolvers/observePosts.spec.ts | 2 +- backend/src/schema/resolvers/posts.spec.ts | 2 +- .../schema/resolvers/postsInGroups.spec.ts | 24 +- backend/src/schema/resolvers/rooms.spec.ts | 6 +- .../schema/resolvers/user_management.spec.ts | 2 +- 45 files changed, 542 insertions(+), 563 deletions(-) delete mode 100644 backend/src/graphql/groups.ts delete mode 100644 backend/src/graphql/messages.ts delete mode 100644 backend/src/graphql/notifications.ts delete mode 100644 backend/src/graphql/posts.ts create mode 100644 backend/src/graphql/queries/changeGroupMemberRoleMutation.ts rename backend/src/graphql/{comments.ts => queries/createCommentMutation.ts} (76%) create mode 100644 backend/src/graphql/queries/createGroupMutation.ts create mode 100644 backend/src/graphql/queries/createMessageMutation.ts create mode 100644 backend/src/graphql/queries/createPostMutation.ts create mode 100644 backend/src/graphql/queries/createRoomMutation.ts create mode 100644 backend/src/graphql/queries/filterPosts.ts create mode 100644 backend/src/graphql/queries/groupMembersQuery.ts create mode 100644 backend/src/graphql/queries/groupQuery.ts create mode 100644 backend/src/graphql/queries/joinGroupMutation.ts create mode 100644 backend/src/graphql/queries/leaveGroupMutation.ts rename backend/src/graphql/{userManagement.ts => queries/loginMutation.ts} (72%) create mode 100644 backend/src/graphql/queries/markAllAsReadMutation.ts create mode 100644 backend/src/graphql/queries/markAsReadMutation.ts create mode 100644 backend/src/graphql/queries/markMessagesAsSeen.ts create mode 100644 backend/src/graphql/queries/messageQuery.ts create mode 100644 backend/src/graphql/queries/notificationQuery.ts create mode 100644 backend/src/graphql/queries/postQuery.ts create mode 100644 backend/src/graphql/queries/profilePagePosts.ts create mode 100644 backend/src/graphql/queries/removeUserFromGroupMutation.ts rename backend/src/graphql/{rooms.ts => queries/roomQuery.ts} (58%) create mode 100644 backend/src/graphql/queries/searchPosts.ts rename backend/src/graphql/{authentications.ts => queries/signupVerificationMutation.ts} (88%) create mode 100644 backend/src/graphql/queries/unreadRoomsQuery.ts create mode 100644 backend/src/graphql/queries/updateGroupMutation.ts diff --git a/backend/src/db/seed.ts b/backend/src/db/seed.ts index b727c85e8..d0aa5bd29 100644 --- a/backend/src/db/seed.ts +++ b/backend/src/db/seed.ts @@ -9,15 +9,13 @@ import sample from 'lodash/sample' import CONFIG from '@config/index' import { categories } from '@constants/categories' -import { createCommentMutation } from '@graphql/comments' -import { - createGroupMutation, - joinGroupMutation, - changeGroupMemberRoleMutation, -} from '@graphql/groups' -import { createMessageMutation } from '@graphql/messages' -import { createPostMutation } from '@graphql/posts' -import { createRoomMutation } from '@graphql/rooms' +import { changeGroupMemberRoleMutation } from '@graphql/queries/changeGroupMemberRoleMutation' +import { createCommentMutation } from '@graphql/queries/createCommentMutation' +import { createGroupMutation } from '@graphql/queries/createGroupMutation' +import { createMessageMutation } from '@graphql/queries/createMessageMutation' +import { createPostMutation } from '@graphql/queries/createPostMutation' +import { createRoomMutation } from '@graphql/queries/createRoomMutation' +import { joinGroupMutation } from '@graphql/queries/joinGroupMutation' import createServer from '@src/server' import Factory from './factories' diff --git a/backend/src/graphql/groups.ts b/backend/src/graphql/groups.ts deleted file mode 100644 index a7cfc3351..000000000 --- a/backend/src/graphql/groups.ts +++ /dev/null @@ -1,216 +0,0 @@ -import gql from 'graphql-tag' - -// ------ mutations - -export const createGroupMutation = () => { - return gql` - mutation ( - $id: ID - $name: String! - $slug: String - $about: String - $description: String! - $groupType: GroupType! - $actionRadius: GroupActionRadius! - $categoryIds: [ID] - $locationName: String # empty string '' sets it to null - ) { - CreateGroup( - id: $id - name: $name - slug: $slug - about: $about - description: $description - groupType: $groupType - actionRadius: $actionRadius - categoryIds: $categoryIds - locationName: $locationName - ) { - id - name - slug - createdAt - updatedAt - disabled - deleted - about - description - descriptionExcerpt - groupType - actionRadius - categories { - id - slug - name - icon - } - locationName - location { - name - nameDE - nameEN - } - myRole - } - } - ` -} - -export const updateGroupMutation = () => { - return gql` - mutation ( - $id: ID! - $name: String - $slug: String - $about: String - $description: String - $actionRadius: GroupActionRadius - $categoryIds: [ID] - $avatar: ImageInput - $locationName: String # empty string '' sets it to null - ) { - UpdateGroup( - id: $id - name: $name - slug: $slug - about: $about - description: $description - actionRadius: $actionRadius - categoryIds: $categoryIds - avatar: $avatar - locationName: $locationName - ) { - id - name - slug - createdAt - updatedAt - disabled - deleted - about - description - descriptionExcerpt - groupType - actionRadius - categories { - id - slug - name - icon - } - # avatar # test this as result - locationName - location { - name - nameDE - nameEN - } - myRole - } - } - ` -} - -export const joinGroupMutation = () => { - return gql` - mutation ($groupId: ID!, $userId: ID!) { - JoinGroup(groupId: $groupId, userId: $userId) { - id - name - slug - myRoleInGroup - } - } - ` -} - -export const leaveGroupMutation = () => { - return gql` - mutation ($groupId: ID!, $userId: ID!) { - LeaveGroup(groupId: $groupId, userId: $userId) { - id - name - slug - myRoleInGroup - } - } - ` -} - -export const changeGroupMemberRoleMutation = () => { - return gql` - mutation ($groupId: ID!, $userId: ID!, $roleInGroup: GroupMemberRole!) { - ChangeGroupMemberRole(groupId: $groupId, userId: $userId, roleInGroup: $roleInGroup) { - id - name - slug - myRoleInGroup - } - } - ` -} - -export const removeUserFromGroupMutation = () => { - return gql` - mutation ($groupId: ID!, $userId: ID!) { - RemoveUserFromGroup(groupId: $groupId, userId: $userId) { - id - name - slug - myRoleInGroup - } - } - ` -} - -// ------ queries - -export const groupQuery = () => { - return gql` - query ($isMember: Boolean, $id: ID, $slug: String) { - Group(isMember: $isMember, id: $id, slug: $slug) { - id - name - slug - createdAt - updatedAt - disabled - deleted - about - description - descriptionExcerpt - groupType - actionRadius - categories { - id - slug - name - icon - } - avatar { - url - } - locationName - location { - name - nameDE - nameEN - } - myRole - } - } - ` -} - -export const groupMembersQuery = () => { - return gql` - query ($id: ID!) { - GroupMembers(id: $id) { - id - name - slug - myRoleInGroup - } - } - ` -} diff --git a/backend/src/graphql/messages.ts b/backend/src/graphql/messages.ts deleted file mode 100644 index 2842c7230..000000000 --- a/backend/src/graphql/messages.ts +++ /dev/null @@ -1,50 +0,0 @@ -import gql from 'graphql-tag' - -export const createMessageMutation = () => { - return gql` - mutation ($roomId: ID!, $content: String!) { - CreateMessage(roomId: $roomId, content: $content) { - id - content - senderId - username - avatar - date - saved - distributed - seen - } - } - ` -} - -export const messageQuery = () => { - return gql` - query ($roomId: ID!, $first: Int, $offset: Int) { - Message(roomId: $roomId, first: $first, offset: $offset, orderBy: indexId_desc) { - _id - id - indexId - content - senderId - author { - id - } - username - avatar - date - saved - distributed - seen - } - } - ` -} - -export const markMessagesAsSeen = () => { - return gql` - mutation ($messageIds: [String!]) { - MarkMessagesAsSeen(messageIds: $messageIds) - } - ` -} diff --git a/backend/src/graphql/notifications.ts b/backend/src/graphql/notifications.ts deleted file mode 100644 index 233077372..000000000 --- a/backend/src/graphql/notifications.ts +++ /dev/null @@ -1,65 +0,0 @@ -import gql from 'graphql-tag' - -// ------ mutations - -export const markAsReadMutation = () => { - return gql` - mutation ($id: ID!) { - markAsRead(id: $id) { - from { - __typename - ... on Post { - content - } - ... on Comment { - content - } - } - read - createdAt - } - } - ` -} - -export const markAllAsReadMutation = () => { - return gql` - mutation { - markAllAsRead { - from { - __typename - ... on Post { - content - } - ... on Comment { - content - } - } - read - createdAt - } - } - ` -} - -// ------ queries - -export const notificationQuery = () => { - return gql` - query ($read: Boolean, $orderBy: NotificationOrdering) { - notifications(read: $read, orderBy: $orderBy) { - from { - __typename - ... on Post { - content - } - ... on Comment { - content - } - } - read - createdAt - } - } - ` -} diff --git a/backend/src/graphql/posts.ts b/backend/src/graphql/posts.ts deleted file mode 100644 index dcd75a4ff..000000000 --- a/backend/src/graphql/posts.ts +++ /dev/null @@ -1,113 +0,0 @@ -import gql from 'graphql-tag' - -// ------ mutations - -export const createPostMutation = () => { - return gql` - mutation ( - $id: ID - $title: String! - $slug: String - $content: String! - $categoryIds: [ID] - $groupId: ID - $postType: PostType - $eventInput: _EventInput - ) { - CreatePost( - id: $id - title: $title - slug: $slug - content: $content - categoryIds: $categoryIds - groupId: $groupId - postType: $postType - eventInput: $eventInput - ) { - id - slug - title - content - disabled - deleted - postType - author { - name - } - categories { - id - } - eventStart - eventEnd - eventLocationName - eventVenue - eventIsOnline - eventLocation { - lng - lat - } - isObservedByMe - observingUsersCount - } - } - ` -} - -// ------ queries - -export const postQuery = () => { - return gql` - query Post($id: ID!) { - Post(id: $id) { - id - title - content - } - } - ` -} - -export const filterPosts = () => { - return gql` - query Post($filter: _PostFilter, $first: Int, $offset: Int, $orderBy: [_PostOrdering]) { - Post(filter: $filter, first: $first, offset: $offset, orderBy: $orderBy) { - id - title - content - eventStart - } - } - ` -} - -export const profilePagePosts = () => { - return gql` - query profilePagePosts( - $filter: _PostFilter - $first: Int - $offset: Int - $orderBy: [_PostOrdering] - ) { - profilePagePosts(filter: $filter, first: $first, offset: $offset, orderBy: $orderBy) { - id - title - content - } - } - ` -} - -export const searchPosts = () => { - return gql` - query ($query: String!, $firstPosts: Int, $postsOffset: Int) { - searchPosts(query: $query, firstPosts: $firstPosts, postsOffset: $postsOffset) { - postCount - posts { - id - title - content - } - } - } - ` -} diff --git a/backend/src/graphql/queries/changeGroupMemberRoleMutation.ts b/backend/src/graphql/queries/changeGroupMemberRoleMutation.ts new file mode 100644 index 000000000..a01c19cfb --- /dev/null +++ b/backend/src/graphql/queries/changeGroupMemberRoleMutation.ts @@ -0,0 +1,14 @@ +import gql from 'graphql-tag' + +export const changeGroupMemberRoleMutation = () => { + return gql` + mutation ($groupId: ID!, $userId: ID!, $roleInGroup: GroupMemberRole!) { + ChangeGroupMemberRole(groupId: $groupId, userId: $userId, roleInGroup: $roleInGroup) { + id + name + slug + myRoleInGroup + } + } + ` +} diff --git a/backend/src/graphql/comments.ts b/backend/src/graphql/queries/createCommentMutation.ts similarity index 76% rename from backend/src/graphql/comments.ts rename to backend/src/graphql/queries/createCommentMutation.ts index b408c5e95..c3824e1d0 100644 --- a/backend/src/graphql/comments.ts +++ b/backend/src/graphql/queries/createCommentMutation.ts @@ -1,7 +1,5 @@ import gql from 'graphql-tag' -// ------ mutations - export const createCommentMutation = gql` mutation ($id: ID, $postId: ID!, $content: String!) { CreateComment(id: $id, postId: $postId, content: $content) { @@ -9,7 +7,3 @@ export const createCommentMutation = gql` } } ` - -// ------ queries - -// fill queries in here diff --git a/backend/src/graphql/queries/createGroupMutation.ts b/backend/src/graphql/queries/createGroupMutation.ts new file mode 100644 index 000000000..20cd93323 --- /dev/null +++ b/backend/src/graphql/queries/createGroupMutation.ts @@ -0,0 +1,55 @@ +import gql from 'graphql-tag' + +export const createGroupMutation = () => { + return gql` + mutation ( + $id: ID + $name: String! + $slug: String + $about: String + $description: String! + $groupType: GroupType! + $actionRadius: GroupActionRadius! + $categoryIds: [ID] + $locationName: String # empty string '' sets it to null + ) { + CreateGroup( + id: $id + name: $name + slug: $slug + about: $about + description: $description + groupType: $groupType + actionRadius: $actionRadius + categoryIds: $categoryIds + locationName: $locationName + ) { + id + name + slug + createdAt + updatedAt + disabled + deleted + about + description + descriptionExcerpt + groupType + actionRadius + categories { + id + slug + name + icon + } + locationName + location { + name + nameDE + nameEN + } + myRole + } + } + ` +} diff --git a/backend/src/graphql/queries/createMessageMutation.ts b/backend/src/graphql/queries/createMessageMutation.ts new file mode 100644 index 000000000..e8c6fc7b8 --- /dev/null +++ b/backend/src/graphql/queries/createMessageMutation.ts @@ -0,0 +1,19 @@ +import gql from 'graphql-tag' + +export const createMessageMutation = () => { + return gql` + mutation ($roomId: ID!, $content: String!) { + CreateMessage(roomId: $roomId, content: $content) { + id + content + senderId + username + avatar + date + saved + distributed + seen + } + } + ` +} diff --git a/backend/src/graphql/queries/createPostMutation.ts b/backend/src/graphql/queries/createPostMutation.ts new file mode 100644 index 000000000..f0a01b303 --- /dev/null +++ b/backend/src/graphql/queries/createPostMutation.ts @@ -0,0 +1,52 @@ +import gql from 'graphql-tag' + +export const createPostMutation = () => { + return gql` + mutation ( + $id: ID + $title: String! + $slug: String + $content: String! + $categoryIds: [ID] + $groupId: ID + $postType: PostType + $eventInput: _EventInput + ) { + CreatePost( + id: $id + title: $title + slug: $slug + content: $content + categoryIds: $categoryIds + groupId: $groupId + postType: $postType + eventInput: $eventInput + ) { + id + slug + title + content + disabled + deleted + postType + author { + name + } + categories { + id + } + eventStart + eventEnd + eventLocationName + eventVenue + eventIsOnline + eventLocation { + lng + lat + } + isObservedByMe + observingUsersCount + } + } + ` +} diff --git a/backend/src/graphql/queries/createRoomMutation.ts b/backend/src/graphql/queries/createRoomMutation.ts new file mode 100644 index 000000000..3a791d294 --- /dev/null +++ b/backend/src/graphql/queries/createRoomMutation.ts @@ -0,0 +1,24 @@ +import gql from 'graphql-tag' + +export const createRoomMutation = () => { + return gql` + mutation ($userId: ID!) { + CreateRoom(userId: $userId) { + id + roomId + roomName + lastMessageAt + unreadCount + #avatar + users { + _id + id + name + avatar { + url + } + } + } + } + ` +} diff --git a/backend/src/graphql/queries/filterPosts.ts b/backend/src/graphql/queries/filterPosts.ts new file mode 100644 index 000000000..7e6d5059f --- /dev/null +++ b/backend/src/graphql/queries/filterPosts.ts @@ -0,0 +1,14 @@ +import gql from 'graphql-tag' + +export const filterPosts = () => { + return gql` + query Post($filter: _PostFilter, $first: Int, $offset: Int, $orderBy: [_PostOrdering]) { + Post(filter: $filter, first: $first, offset: $offset, orderBy: $orderBy) { + id + title + content + eventStart + } + } + ` +} diff --git a/backend/src/graphql/queries/groupMembersQuery.ts b/backend/src/graphql/queries/groupMembersQuery.ts new file mode 100644 index 000000000..b1b8cb313 --- /dev/null +++ b/backend/src/graphql/queries/groupMembersQuery.ts @@ -0,0 +1,14 @@ +import gql from 'graphql-tag' + +export const groupMembersQuery = () => { + return gql` + query ($id: ID!) { + GroupMembers(id: $id) { + id + name + slug + myRoleInGroup + } + } + ` +} diff --git a/backend/src/graphql/queries/groupQuery.ts b/backend/src/graphql/queries/groupQuery.ts new file mode 100644 index 000000000..463e9e13e --- /dev/null +++ b/backend/src/graphql/queries/groupQuery.ts @@ -0,0 +1,38 @@ +import gql from 'graphql-tag' + +export const groupQuery = () => { + return gql` + query ($isMember: Boolean, $id: ID, $slug: String) { + Group(isMember: $isMember, id: $id, slug: $slug) { + id + name + slug + createdAt + updatedAt + disabled + deleted + about + description + descriptionExcerpt + groupType + actionRadius + categories { + id + slug + name + icon + } + avatar { + url + } + locationName + location { + name + nameDE + nameEN + } + myRole + } + } + ` +} diff --git a/backend/src/graphql/queries/joinGroupMutation.ts b/backend/src/graphql/queries/joinGroupMutation.ts new file mode 100644 index 000000000..ce627b1ef --- /dev/null +++ b/backend/src/graphql/queries/joinGroupMutation.ts @@ -0,0 +1,14 @@ +import gql from 'graphql-tag' + +export const joinGroupMutation = () => { + return gql` + mutation ($groupId: ID!, $userId: ID!) { + JoinGroup(groupId: $groupId, userId: $userId) { + id + name + slug + myRoleInGroup + } + } + ` +} diff --git a/backend/src/graphql/queries/leaveGroupMutation.ts b/backend/src/graphql/queries/leaveGroupMutation.ts new file mode 100644 index 000000000..470bd6a7a --- /dev/null +++ b/backend/src/graphql/queries/leaveGroupMutation.ts @@ -0,0 +1,14 @@ +import gql from 'graphql-tag' + +export const leaveGroupMutation = () => { + return gql` + mutation ($groupId: ID!, $userId: ID!) { + LeaveGroup(groupId: $groupId, userId: $userId) { + id + name + slug + myRoleInGroup + } + } + ` +} diff --git a/backend/src/graphql/userManagement.ts b/backend/src/graphql/queries/loginMutation.ts similarity index 72% rename from backend/src/graphql/userManagement.ts rename to backend/src/graphql/queries/loginMutation.ts index 3cb8a05f8..8c7b36f12 100644 --- a/backend/src/graphql/userManagement.ts +++ b/backend/src/graphql/queries/loginMutation.ts @@ -1,13 +1,7 @@ import gql from 'graphql-tag' -// ------ mutations - export const loginMutation = gql` mutation ($email: String!, $password: String!) { login(email: $email, password: $password) } ` - -// ------ queries - -// fill queries in here diff --git a/backend/src/graphql/queries/markAllAsReadMutation.ts b/backend/src/graphql/queries/markAllAsReadMutation.ts new file mode 100644 index 000000000..d1f19e369 --- /dev/null +++ b/backend/src/graphql/queries/markAllAsReadMutation.ts @@ -0,0 +1,21 @@ +import gql from 'graphql-tag' + +export const markAllAsReadMutation = () => { + return gql` + mutation { + markAllAsRead { + from { + __typename + ... on Post { + content + } + ... on Comment { + content + } + } + read + createdAt + } + } + ` +} diff --git a/backend/src/graphql/queries/markAsReadMutation.ts b/backend/src/graphql/queries/markAsReadMutation.ts new file mode 100644 index 000000000..fd855665a --- /dev/null +++ b/backend/src/graphql/queries/markAsReadMutation.ts @@ -0,0 +1,21 @@ +import gql from 'graphql-tag' + +export const markAsReadMutation = () => { + return gql` + mutation ($id: ID!) { + markAsRead(id: $id) { + from { + __typename + ... on Post { + content + } + ... on Comment { + content + } + } + read + createdAt + } + } + ` +} diff --git a/backend/src/graphql/queries/markMessagesAsSeen.ts b/backend/src/graphql/queries/markMessagesAsSeen.ts new file mode 100644 index 000000000..9081c5def --- /dev/null +++ b/backend/src/graphql/queries/markMessagesAsSeen.ts @@ -0,0 +1,9 @@ +import gql from 'graphql-tag' + +export const markMessagesAsSeen = () => { + return gql` + mutation ($messageIds: [String!]) { + MarkMessagesAsSeen(messageIds: $messageIds) + } + ` +} diff --git a/backend/src/graphql/queries/messageQuery.ts b/backend/src/graphql/queries/messageQuery.ts new file mode 100644 index 000000000..791851121 --- /dev/null +++ b/backend/src/graphql/queries/messageQuery.ts @@ -0,0 +1,24 @@ +import gql from 'graphql-tag' + +export const messageQuery = () => { + return gql` + query ($roomId: ID!, $first: Int, $offset: Int) { + Message(roomId: $roomId, first: $first, offset: $offset, orderBy: indexId_desc) { + _id + id + indexId + content + senderId + author { + id + } + username + avatar + date + saved + distributed + seen + } + } + ` +} diff --git a/backend/src/graphql/queries/notificationQuery.ts b/backend/src/graphql/queries/notificationQuery.ts new file mode 100644 index 000000000..965fb9ce9 --- /dev/null +++ b/backend/src/graphql/queries/notificationQuery.ts @@ -0,0 +1,21 @@ +import gql from 'graphql-tag' + +export const notificationQuery = () => { + return gql` + query ($read: Boolean, $orderBy: NotificationOrdering) { + notifications(read: $read, orderBy: $orderBy) { + from { + __typename + ... on Post { + content + } + ... on Comment { + content + } + } + read + createdAt + } + } + ` +} diff --git a/backend/src/graphql/queries/postQuery.ts b/backend/src/graphql/queries/postQuery.ts new file mode 100644 index 000000000..ff8faf311 --- /dev/null +++ b/backend/src/graphql/queries/postQuery.ts @@ -0,0 +1,13 @@ +import gql from 'graphql-tag' + +export const postQuery = () => { + return gql` + query Post($id: ID!) { + Post(id: $id) { + id + title + content + } + } + ` +} diff --git a/backend/src/graphql/queries/profilePagePosts.ts b/backend/src/graphql/queries/profilePagePosts.ts new file mode 100644 index 000000000..5d713a23c --- /dev/null +++ b/backend/src/graphql/queries/profilePagePosts.ts @@ -0,0 +1,18 @@ +import gql from 'graphql-tag' + +export const profilePagePosts = () => { + return gql` + query profilePagePosts( + $filter: _PostFilter + $first: Int + $offset: Int + $orderBy: [_PostOrdering] + ) { + profilePagePosts(filter: $filter, first: $first, offset: $offset, orderBy: $orderBy) { + id + title + content + } + } + ` +} diff --git a/backend/src/graphql/queries/removeUserFromGroupMutation.ts b/backend/src/graphql/queries/removeUserFromGroupMutation.ts new file mode 100644 index 000000000..bdb9792d9 --- /dev/null +++ b/backend/src/graphql/queries/removeUserFromGroupMutation.ts @@ -0,0 +1,14 @@ +import gql from 'graphql-tag' + +export const removeUserFromGroupMutation = () => { + return gql` + mutation ($groupId: ID!, $userId: ID!) { + RemoveUserFromGroup(groupId: $groupId, userId: $userId) { + id + name + slug + myRoleInGroup + } + } + ` +} diff --git a/backend/src/graphql/rooms.ts b/backend/src/graphql/queries/roomQuery.ts similarity index 58% rename from backend/src/graphql/rooms.ts rename to backend/src/graphql/queries/roomQuery.ts index 7612641f3..01b24654e 100644 --- a/backend/src/graphql/rooms.ts +++ b/backend/src/graphql/queries/roomQuery.ts @@ -1,28 +1,5 @@ import gql from 'graphql-tag' -export const createRoomMutation = () => { - return gql` - mutation ($userId: ID!) { - CreateRoom(userId: $userId) { - id - roomId - roomName - lastMessageAt - unreadCount - #avatar - users { - _id - id - name - avatar { - url - } - } - } - } - ` -} - export const roomQuery = () => { return gql` query Room($first: Int, $offset: Int, $id: ID) { @@ -57,11 +34,3 @@ export const roomQuery = () => { } ` } - -export const unreadRoomsQuery = () => { - return gql` - query { - UnreadRooms - } - ` -} diff --git a/backend/src/graphql/queries/searchPosts.ts b/backend/src/graphql/queries/searchPosts.ts new file mode 100644 index 000000000..ed9e9a641 --- /dev/null +++ b/backend/src/graphql/queries/searchPosts.ts @@ -0,0 +1,16 @@ +import gql from 'graphql-tag' + +export const searchPosts = () => { + return gql` + query ($query: String!, $firstPosts: Int, $postsOffset: Int) { + searchPosts(query: $query, firstPosts: $firstPosts, postsOffset: $postsOffset) { + postCount + posts { + id + title + content + } + } + } + ` +} diff --git a/backend/src/graphql/authentications.ts b/backend/src/graphql/queries/signupVerificationMutation.ts similarity index 88% rename from backend/src/graphql/authentications.ts rename to backend/src/graphql/queries/signupVerificationMutation.ts index 91605ec9f..f504da0ce 100644 --- a/backend/src/graphql/authentications.ts +++ b/backend/src/graphql/queries/signupVerificationMutation.ts @@ -1,7 +1,5 @@ import gql from 'graphql-tag' -// ------ mutations - export const signupVerificationMutation = gql` mutation ( $password: String! @@ -24,7 +22,3 @@ export const signupVerificationMutation = gql` } } ` - -// ------ queries - -// fill queries in here diff --git a/backend/src/graphql/queries/unreadRoomsQuery.ts b/backend/src/graphql/queries/unreadRoomsQuery.ts new file mode 100644 index 000000000..d5612dcad --- /dev/null +++ b/backend/src/graphql/queries/unreadRoomsQuery.ts @@ -0,0 +1,9 @@ +import gql from 'graphql-tag' + +export const unreadRoomsQuery = () => { + return gql` + query { + UnreadRooms + } + ` +} diff --git a/backend/src/graphql/queries/updateGroupMutation.ts b/backend/src/graphql/queries/updateGroupMutation.ts new file mode 100644 index 000000000..826a9c9d4 --- /dev/null +++ b/backend/src/graphql/queries/updateGroupMutation.ts @@ -0,0 +1,56 @@ +import gql from 'graphql-tag' + +export const updateGroupMutation = () => { + return gql` + mutation ( + $id: ID! + $name: String + $slug: String + $about: String + $description: String + $actionRadius: GroupActionRadius + $categoryIds: [ID] + $avatar: ImageInput + $locationName: String # empty string '' sets it to null + ) { + UpdateGroup( + id: $id + name: $name + slug: $slug + about: $about + description: $description + actionRadius: $actionRadius + categoryIds: $categoryIds + avatar: $avatar + locationName: $locationName + ) { + id + name + slug + createdAt + updatedAt + disabled + deleted + about + description + descriptionExcerpt + groupType + actionRadius + categories { + id + slug + name + icon + } + # avatar # test this as result + locationName + location { + name + nameDE + nameEN + } + myRole + } + } + ` +} diff --git a/backend/src/middleware/notifications/notificationsMiddleware.emails.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.emails.spec.ts index 097917d1e..78c95b454 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.emails.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.emails.spec.ts @@ -9,7 +9,8 @@ import gql from 'graphql-tag' import Factory, { cleanDatabase } from '@db/factories' import { getNeode, getDriver } from '@db/neo4j' -import { createGroupMutation, joinGroupMutation } from '@graphql/groups' +import { createGroupMutation } from '@graphql/queries/createGroupMutation' +import { joinGroupMutation } from '@graphql/queries/joinGroupMutation' import CONFIG from '@src/config' import createServer from '@src/server' diff --git a/backend/src/middleware/notifications/notificationsMiddleware.followed-users.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.followed-users.spec.ts index c2b1419f4..5be4ea5b5 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.followed-users.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.followed-users.spec.ts @@ -8,7 +8,7 @@ import gql from 'graphql-tag' import Factory, { cleanDatabase } from '@db/factories' import { getNeode, getDriver } from '@db/neo4j' -import { createGroupMutation } from '@graphql/groups' +import { createGroupMutation } from '@graphql/queries/createGroupMutation' import CONFIG from '@src/config' import createServer from '@src/server' diff --git a/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts index 5fce124d9..7058efd25 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts @@ -9,11 +9,9 @@ import gql from 'graphql-tag' import Factory, { cleanDatabase } from '@db/factories' import { getNeode, getDriver } from '@db/neo4j' -import { - createGroupMutation, - joinGroupMutation, - changeGroupMemberRoleMutation, -} from '@graphql/groups' +import { changeGroupMemberRoleMutation } from '@graphql/queries/changeGroupMemberRoleMutation' +import { createGroupMutation } from '@graphql/queries/createGroupMutation' +import { joinGroupMutation } from '@graphql/queries/joinGroupMutation' import CONFIG from '@src/config' import createServer from '@src/server' diff --git a/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts index 0e4ccad5a..9ca4ae7ab 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts @@ -9,11 +9,9 @@ import gql from 'graphql-tag' import Factory, { cleanDatabase } from '@db/factories' import { getNeode, getDriver } from '@db/neo4j' -import { - createGroupMutation, - joinGroupMutation, - changeGroupMemberRoleMutation, -} from '@graphql/groups' +import { changeGroupMemberRoleMutation } from '@graphql/queries/changeGroupMemberRoleMutation' +import { createGroupMutation } from '@graphql/queries/createGroupMutation' +import { joinGroupMutation } from '@graphql/queries/joinGroupMutation' import CONFIG from '@src/config' import createServer from '@src/server' diff --git a/backend/src/middleware/notifications/notificationsMiddleware.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.spec.ts index 859a67544..908ccac22 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.spec.ts @@ -9,15 +9,13 @@ import gql from 'graphql-tag' import Factory, { cleanDatabase } from '@db/factories' import { getNeode, getDriver } from '@db/neo4j' -import { - createGroupMutation, - joinGroupMutation, - leaveGroupMutation, - changeGroupMemberRoleMutation, - removeUserFromGroupMutation, -} from '@graphql/groups' -import { createMessageMutation } from '@graphql/messages' -import { createRoomMutation } from '@graphql/rooms' +import { changeGroupMemberRoleMutation } from '@graphql/queries/changeGroupMemberRoleMutation' +import { createGroupMutation } from '@graphql/queries/createGroupMutation' +import { createMessageMutation } from '@graphql/queries/createMessageMutation' +import { createRoomMutation } from '@graphql/queries/createRoomMutation' +import { joinGroupMutation } from '@graphql/queries/joinGroupMutation' +import { leaveGroupMutation } from '@graphql/queries/leaveGroupMutation' +import { removeUserFromGroupMutation } from '@graphql/queries/removeUserFromGroupMutation' import createServer, { pubsub } from '@src/server' const sendMailMock = jest.fn() diff --git a/backend/src/middleware/slugifyMiddleware.spec.ts b/backend/src/middleware/slugifyMiddleware.spec.ts index 496a90568..35247471c 100644 --- a/backend/src/middleware/slugifyMiddleware.spec.ts +++ b/backend/src/middleware/slugifyMiddleware.spec.ts @@ -6,9 +6,10 @@ import { createTestClient } from 'apollo-server-testing' import Factory, { cleanDatabase } from '@db/factories' import { getNeode, getDriver } from '@db/neo4j' -import { signupVerificationMutation } from '@graphql/authentications' -import { createGroupMutation, updateGroupMutation } from '@graphql/groups' -import { createPostMutation } from '@graphql/posts' +import { createGroupMutation } from '@graphql/queries/createGroupMutation' +import { createPostMutation } from '@graphql/queries/createPostMutation' +import { signupVerificationMutation } from '@graphql/queries/signupVerificationMutation' +import { updateGroupMutation } from '@graphql/queries/updateGroupMutation' import createServer from '@src/server' let authenticatedUser diff --git a/backend/src/schema/resolvers/filter-posts.spec.ts b/backend/src/schema/resolvers/filter-posts.spec.ts index 83421bc37..87ba2a8e5 100644 --- a/backend/src/schema/resolvers/filter-posts.spec.ts +++ b/backend/src/schema/resolvers/filter-posts.spec.ts @@ -6,7 +6,8 @@ import { createTestClient } from 'apollo-server-testing' import CONFIG from '@config/index' import Factory, { cleanDatabase } from '@db/factories' import { getNeode, getDriver } from '@db/neo4j' -import { filterPosts, createPostMutation } from '@graphql/posts' +import { createPostMutation } from '@graphql/queries/createPostMutation' +import { filterPosts } from '@graphql/queries/filterPosts' import createServer from '@src/server' CONFIG.CATEGORIES_ACTIVE = false diff --git a/backend/src/schema/resolvers/groups.spec.ts b/backend/src/schema/resolvers/groups.spec.ts index c8ce48c37..39ab87dd4 100644 --- a/backend/src/schema/resolvers/groups.spec.ts +++ b/backend/src/schema/resolvers/groups.spec.ts @@ -8,16 +8,14 @@ import { createTestClient } from 'apollo-server-testing' import CONFIG from '@config/index' import Factory, { cleanDatabase } from '@db/factories' import { getNeode, getDriver } from '@db/neo4j' -import { - createGroupMutation, - updateGroupMutation, - joinGroupMutation, - leaveGroupMutation, - changeGroupMemberRoleMutation, - removeUserFromGroupMutation, - groupMembersQuery, - groupQuery, -} from '@graphql/groups' +import { changeGroupMemberRoleMutation } from '@graphql/queries/changeGroupMemberRoleMutation' +import { createGroupMutation } from '@graphql/queries/createGroupMutation' +import { groupMembersQuery } from '@graphql/queries/groupMembersQuery' +import { groupQuery } from '@graphql/queries/groupQuery' +import { joinGroupMutation } from '@graphql/queries/joinGroupMutation' +import { leaveGroupMutation } from '@graphql/queries/leaveGroupMutation' +import { removeUserFromGroupMutation } from '@graphql/queries/removeUserFromGroupMutation' +import { updateGroupMutation } from '@graphql/queries/updateGroupMutation' import createServer from '@src/server' const driver = getDriver() diff --git a/backend/src/schema/resolvers/messages.spec.ts b/backend/src/schema/resolvers/messages.spec.ts index c4aac03f8..7b46e0205 100644 --- a/backend/src/schema/resolvers/messages.spec.ts +++ b/backend/src/schema/resolvers/messages.spec.ts @@ -7,8 +7,11 @@ import { createTestClient } from 'apollo-server-testing' import Factory, { cleanDatabase } from '@db/factories' import { getNeode, getDriver } from '@db/neo4j' -import { createMessageMutation, messageQuery, markMessagesAsSeen } from '@graphql/messages' -import { createRoomMutation, roomQuery } from '@graphql/rooms' +import { createMessageMutation } from '@graphql/queries/createMessageMutation' +import { createRoomMutation } from '@graphql/queries/createRoomMutation' +import { markMessagesAsSeen } from '@graphql/queries/markMessagesAsSeen' +import { messageQuery } from '@graphql/queries/messageQuery' +import { roomQuery } from '@graphql/queries/roomQuery' import createServer, { pubsub } from '@src/server' const driver = getDriver() diff --git a/backend/src/schema/resolvers/notifications.spec.ts b/backend/src/schema/resolvers/notifications.spec.ts index 7678a547d..2aebe4c24 100644 --- a/backend/src/schema/resolvers/notifications.spec.ts +++ b/backend/src/schema/resolvers/notifications.spec.ts @@ -8,11 +8,9 @@ import gql from 'graphql-tag' import Factory, { cleanDatabase } from '@db/factories' import { getDriver } from '@db/neo4j' -import { - markAsReadMutation, - markAllAsReadMutation, - notificationQuery, -} from '@graphql/notifications' +import { markAllAsReadMutation } from '@graphql/queries/markAllAsReadMutation' +import { markAsReadMutation } from '@graphql/queries/markAsReadMutation' +import { notificationQuery } from '@graphql/queries/notificationQuery' import createServer from '@src/server' const driver = getDriver() diff --git a/backend/src/schema/resolvers/observePosts.spec.ts b/backend/src/schema/resolvers/observePosts.spec.ts index bf58ef5ab..9176d424e 100644 --- a/backend/src/schema/resolvers/observePosts.spec.ts +++ b/backend/src/schema/resolvers/observePosts.spec.ts @@ -7,7 +7,7 @@ import gql from 'graphql-tag' import CONFIG from '@config/index' import Factory, { cleanDatabase } from '@db/factories' import { getNeode, getDriver } from '@db/neo4j' -import { createPostMutation } from '@graphql/posts' +import { createPostMutation } from '@graphql/queries/createPostMutation' import createServer from '@src/server' CONFIG.CATEGORIES_ACTIVE = false diff --git a/backend/src/schema/resolvers/posts.spec.ts b/backend/src/schema/resolvers/posts.spec.ts index 9c4f5ebad..108ff51c5 100644 --- a/backend/src/schema/resolvers/posts.spec.ts +++ b/backend/src/schema/resolvers/posts.spec.ts @@ -9,7 +9,7 @@ import gql from 'graphql-tag' import CONFIG from '@config/index' import Factory, { cleanDatabase } from '@db/factories' import { getNeode, getDriver } from '@db/neo4j' -import { createPostMutation } from '@graphql/posts' +import { createPostMutation } from '@graphql/queries/createPostMutation' import createServer from '@src/server' CONFIG.CATEGORIES_ACTIVE = true diff --git a/backend/src/schema/resolvers/postsInGroups.spec.ts b/backend/src/schema/resolvers/postsInGroups.spec.ts index ca4d45f96..664a64b9f 100644 --- a/backend/src/schema/resolvers/postsInGroups.spec.ts +++ b/backend/src/schema/resolvers/postsInGroups.spec.ts @@ -7,20 +7,16 @@ import { createTestClient } from 'apollo-server-testing' import CONFIG from '@config/index' import Factory, { cleanDatabase } from '@db/factories' import { getNeode, getDriver } from '@db/neo4j' -import { signupVerificationMutation } from '@graphql/authentications' -import { createCommentMutation } from '@graphql/comments' -import { - createGroupMutation, - changeGroupMemberRoleMutation, - leaveGroupMutation, -} from '@graphql/groups' -import { - createPostMutation, - postQuery, - filterPosts, - profilePagePosts, - searchPosts, -} from '@graphql/posts' +import { changeGroupMemberRoleMutation } from '@graphql/queries/changeGroupMemberRoleMutation' +import { createCommentMutation } from '@graphql/queries/createCommentMutation' +import { createGroupMutation } from '@graphql/queries/createGroupMutation' +import { createPostMutation } from '@graphql/queries/createPostMutation' +import { filterPosts } from '@graphql/queries/filterPosts' +import { leaveGroupMutation } from '@graphql/queries/leaveGroupMutation' +import { postQuery } from '@graphql/queries/postQuery' +import { profilePagePosts } from '@graphql/queries/profilePagePosts' +import { searchPosts } from '@graphql/queries/searchPosts' +import { signupVerificationMutation } from '@graphql/queries/signupVerificationMutation' import createServer from '@src/server' CONFIG.CATEGORIES_ACTIVE = false diff --git a/backend/src/schema/resolvers/rooms.spec.ts b/backend/src/schema/resolvers/rooms.spec.ts index 81c75cda5..f374285e1 100644 --- a/backend/src/schema/resolvers/rooms.spec.ts +++ b/backend/src/schema/resolvers/rooms.spec.ts @@ -5,8 +5,10 @@ import { createTestClient } from 'apollo-server-testing' import Factory, { cleanDatabase } from '@db/factories' import { getNeode, getDriver } from '@db/neo4j' -import { createMessageMutation } from '@graphql/messages' -import { createRoomMutation, roomQuery, unreadRoomsQuery } from '@graphql/rooms' +import { createMessageMutation } from '@graphql/queries/createMessageMutation' +import { createRoomMutation } from '@graphql/queries/createRoomMutation' +import { roomQuery } from '@graphql/queries/roomQuery' +import { unreadRoomsQuery } from '@graphql/queries/unreadRoomsQuery' import createServer from '@src/server' const driver = getDriver() diff --git a/backend/src/schema/resolvers/user_management.spec.ts b/backend/src/schema/resolvers/user_management.spec.ts index f76a1f9fb..92832baa2 100644 --- a/backend/src/schema/resolvers/user_management.spec.ts +++ b/backend/src/schema/resolvers/user_management.spec.ts @@ -12,7 +12,7 @@ import CONFIG from '@config/index' import { categories } from '@constants/categories' import Factory, { cleanDatabase } from '@db/factories' import { getNeode, getDriver } from '@db/neo4j' -import { loginMutation } from '@graphql/userManagement' +import { loginMutation } from '@graphql/queries/loginMutation' import encode from '@jwt/encode' import createServer, { context } from '@src/server' From 908344c7c0b0a7083d42b763b4a8b835ab7d4563 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 17:13:25 +0000 Subject: [PATCH 20/54] build(deps): bump actions/setup-node from 4.3.0 to 4.4.0 (#8412) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4.3.0 to 4.4.0. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/cdca7365b2dadb8aad0a33bc7601856ffabcc48e...49933ea5288caeca8642d1e84afbd3f7d6820020) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: 4.4.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-documentation.yml | 2 +- .github/workflows/deploy-documentation.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-documentation.yml b/.github/workflows/check-documentation.yml index 27e761fa5..6b2cddeac 100644 --- a/.github/workflows/check-documentation.yml +++ b/.github/workflows/check-documentation.yml @@ -54,7 +54,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7 - name: Setup Node 20 - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.0.3 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.0.3 with: node-version: '20' diff --git a/.github/workflows/deploy-documentation.yml b/.github/workflows/deploy-documentation.yml index d1816bddd..13e0fb963 100644 --- a/.github/workflows/deploy-documentation.yml +++ b/.github/workflows/deploy-documentation.yml @@ -30,7 +30,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7 - name: Setup Node 20 - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.0.3 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.0.3 with: node-version: 20 From a683b79a5fbb028970e1cc42771fb1f7baace668 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 19:02:22 +0000 Subject: [PATCH 21/54] build(deps-dev): bump cypress from 14.3.0 to 14.3.1 in the cypress group (#8413) Bumps the cypress group with 1 update: [cypress](https://github.com/cypress-io/cypress). Updates `cypress` from 14.3.0 to 14.3.1 - [Release notes](https://github.com/cypress-io/cypress/releases) - [Changelog](https://github.com/cypress-io/cypress/blob/develop/CHANGELOG.md) - [Commits](https://github.com/cypress-io/cypress/compare/v14.3.0...v14.3.1) --- updated-dependencies: - dependency-name: cypress dependency-version: 14.3.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: cypress ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4e02f555e..e358e37f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "auto-changelog": "^2.5.0", "bcryptjs": "^3.0.2", "cross-env": "^7.0.3", - "cypress": "^14.3.0", + "cypress": "^14.3.1", "cypress-network-idle": "^1.15.0", "date-fns": "^3.6.0", "dotenv": "^16.5.0", @@ -7762,9 +7762,9 @@ "optional": true }, "node_modules/cypress": { - "version": "14.3.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-14.3.0.tgz", - "integrity": "sha512-rRfPl9Z0/CczuYybBEoLbDVuT1OGkhYaJ0+urRCshgiDRz6QnoA0KQIQnPx7MJ3zy+VCsbUU1pV74n+6cbJEdg==", + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-14.3.1.tgz", + "integrity": "sha512-/2q06qvHMK3PNiadnRW1Je0lJ43gAFPQJUAK2zIxjr22kugtWxVQznTBLVu1AvRH+RP3oWZhCdWqiEi+0NuqCg==", "dev": true, "hasInstallScript": true, "license": "MIT", diff --git a/package.json b/package.json index 7ddc60707..ffba959af 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "auto-changelog": "^2.5.0", "bcryptjs": "^3.0.2", "cross-env": "^7.0.3", - "cypress": "^14.3.0", + "cypress": "^14.3.1", "cypress-network-idle": "^1.15.0", "date-fns": "^3.6.0", "dotenv": "^16.5.0", diff --git a/yarn.lock b/yarn.lock index 768fbb1a7..ef1abc5f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4074,10 +4074,10 @@ cypress-network-idle@^1.15.0: resolved "https://registry.yarnpkg.com/cypress-network-idle/-/cypress-network-idle-1.15.0.tgz#e249f08695a46f1ddce18a95d5293937f277cbb3" integrity sha512-8zU16zhc7S3nMl1NTEEcNsZYlJy/ZzP2zPTTrngGxyXH32Ipake/xfHLZsgrzeWCieiS2AVhQsakhWqFzO3hpw== -cypress@^14.3.0: - version "14.3.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-14.3.0.tgz#720923501ca0e371d344b810572cea58d5b50aec" - integrity sha512-rRfPl9Z0/CczuYybBEoLbDVuT1OGkhYaJ0+urRCshgiDRz6QnoA0KQIQnPx7MJ3zy+VCsbUU1pV74n+6cbJEdg== +cypress@^14.3.1: + version "14.3.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-14.3.1.tgz#b0570c0e5b198d930a2c0f640d099e777bec2d2f" + integrity sha512-/2q06qvHMK3PNiadnRW1Je0lJ43gAFPQJUAK2zIxjr22kugtWxVQznTBLVu1AvRH+RP3oWZhCdWqiEi+0NuqCg== dependencies: "@cypress/request" "^3.0.8" "@cypress/xvfb" "^1.2.4" From cd044bbd0cd646a06443c957a5a353e793d88cf1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 19:35:09 +0000 Subject: [PATCH 22/54] build(deps): bump sanitize-html from 2.15.0 to 2.16.0 in /backend (#8418) Bumps [sanitize-html](https://github.com/apostrophecms/sanitize-html) from 2.15.0 to 2.16.0. - [Changelog](https://github.com/apostrophecms/sanitize-html/blob/main/CHANGELOG.md) - [Commits](https://github.com/apostrophecms/sanitize-html/compare/2.15.0...2.16.0) --- updated-dependencies: - dependency-name: sanitize-html dependency-version: 2.16.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- backend/package.json | 2 +- backend/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/package.json b/backend/package.json index bc757fbb7..7ebdb57b2 100644 --- a/backend/package.json +++ b/backend/package.json @@ -92,7 +92,7 @@ "nodemailer": "^6.10.1", "nodemailer-html-to-text": "^3.2.0", "request": "~2.88.2", - "sanitize-html": "~2.15.0", + "sanitize-html": "~2.16.0", "slug": "~9.1.0", "subscriptions-transport-ws": "^0.9.19", "trunc-html": "~1.1.2", diff --git a/backend/yarn.lock b/backend/yarn.lock index cd2ce1050..3bed8038a 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -8905,10 +8905,10 @@ safe-regex@^2.1.1: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sanitize-html@~2.15.0: - version "2.15.0" - resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.15.0.tgz#8e7f97ee1fecdec1bb1fb2e37f6d2c6acfdbabe3" - integrity sha512-wIjst57vJGpLyBP8ioUbg6ThwJie5SuSIjHxJg53v5Fg+kUK+AXlb7bK3RNXpp315MvwM+0OBGCV6h5pPHsVhA== +sanitize-html@~2.16.0: + version "2.16.0" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.16.0.tgz#2b9973b63fa42e3580020499cbda1d894b3642bc" + integrity sha512-0s4caLuHHaZFVxFTG74oW91+j6vW7gKbGD6CD2+miP73CE6z6YtOBN0ArtLd2UGyi4IC7K47v3ENUbQX4jV3Mg== dependencies: deepmerge "^4.2.2" escape-string-regexp "^4.0.0" From 8aca541bfb7d1edaf8ef7770b09de92b625f2db9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 20:10:10 +0000 Subject: [PATCH 23/54] build(deps-dev): bump @faker-js/faker from 9.6.0 to 9.7.0 (#8414) Bumps [@faker-js/faker](https://github.com/faker-js/faker) from 9.6.0 to 9.7.0. - [Release notes](https://github.com/faker-js/faker/releases) - [Changelog](https://github.com/faker-js/faker/blob/next/CHANGELOG.md) - [Commits](https://github.com/faker-js/faker/compare/v9.6.0...v9.7.0) --- updated-dependencies: - dependency-name: "@faker-js/faker" dependency-version: 9.7.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index e358e37f4..4174bf891 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "@badeball/cypress-cucumber-preprocessor": "^22.0.1", "@cucumber/cucumber": "11.2.0", "@cypress/browserify-preprocessor": "^3.0.2", - "@faker-js/faker": "9.6.0", + "@faker-js/faker": "9.7.0", "auto-changelog": "^2.5.0", "bcryptjs": "^3.0.2", "cross-env": "^7.0.3", @@ -2786,9 +2786,9 @@ } }, "node_modules/@faker-js/faker": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.6.0.tgz", - "integrity": "sha512-3vm4by+B5lvsFPSyep3ELWmZfE3kicDtmemVpuwl1yH7tqtnHdsA6hG8fbXedMVdkzgtvzWoRgjSB4Q+FHnZiw==", + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.7.0.tgz", + "integrity": "sha512-aozo5vqjCmDoXLNUJarFZx2IN/GgGaogY4TMJ6so/WLZOWpSV7fvj2dmrV6sEAnUm1O7aCrhTibjpzeDFgNqbg==", "dev": true, "funding": [ { diff --git a/package.json b/package.json index ffba959af..e86458b61 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "@badeball/cypress-cucumber-preprocessor": "^22.0.1", "@cucumber/cucumber": "11.2.0", "@cypress/browserify-preprocessor": "^3.0.2", - "@faker-js/faker": "9.6.0", + "@faker-js/faker": "9.7.0", "auto-changelog": "^2.5.0", "bcryptjs": "^3.0.2", "cross-env": "^7.0.3", diff --git a/yarn.lock b/yarn.lock index ef1abc5f9..a5a09d086 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1543,10 +1543,10 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz#81fd50d11e2c32b2d6241470e3185b70c7b30699" integrity sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg== -"@faker-js/faker@9.6.0": - version "9.6.0" - resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.6.0.tgz#64235d20330b142eef3d1d1638ba56c083b4bf1d" - integrity sha512-3vm4by+B5lvsFPSyep3ELWmZfE3kicDtmemVpuwl1yH7tqtnHdsA6hG8fbXedMVdkzgtvzWoRgjSB4Q+FHnZiw== +"@faker-js/faker@9.7.0": + version "9.7.0" + resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.7.0.tgz#1cf1fecfcad5e2da2332140bf3b5f23cc1c2a7f4" + integrity sha512-aozo5vqjCmDoXLNUJarFZx2IN/GgGaogY4TMJ6so/WLZOWpSV7fvj2dmrV6sEAnUm1O7aCrhTibjpzeDFgNqbg== "@fastify/busboy@^2.0.0": version "2.1.1" From c0918f2c355dcf51686c577f09c90cbe2c4dfbc9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 21:21:28 +0000 Subject: [PATCH 24/54] build(deps-dev): bump @faker-js/faker from 9.6.0 to 9.7.0 in /webapp (#8411) Bumps [@faker-js/faker](https://github.com/faker-js/faker) from 9.6.0 to 9.7.0. - [Release notes](https://github.com/faker-js/faker/releases) - [Changelog](https://github.com/faker-js/faker/blob/next/CHANGELOG.md) - [Commits](https://github.com/faker-js/faker/compare/v9.6.0...v9.7.0) --- updated-dependencies: - dependency-name: "@faker-js/faker" dependency-version: 9.7.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- webapp/package.json | 2 +- webapp/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/webapp/package.json b/webapp/package.json index 45d1778b0..d18a88408 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -74,7 +74,7 @@ "@babel/core": "^7.25.8", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/preset-env": "^7.25.8", - "@faker-js/faker": "9.6.0", + "@faker-js/faker": "9.7.0", "@storybook/addon-a11y": "^8.0.8", "@storybook/addon-actions": "^5.3.21", "@storybook/addon-notes": "^5.3.18", diff --git a/webapp/yarn.lock b/webapp/yarn.lock index b8a8d4876..1ef19363e 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -2454,10 +2454,10 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@faker-js/faker@9.6.0": - version "9.6.0" - resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.6.0.tgz#64235d20330b142eef3d1d1638ba56c083b4bf1d" - integrity sha512-3vm4by+B5lvsFPSyep3ELWmZfE3kicDtmemVpuwl1yH7tqtnHdsA6hG8fbXedMVdkzgtvzWoRgjSB4Q+FHnZiw== +"@faker-js/faker@9.7.0": + version "9.7.0" + resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.7.0.tgz#1cf1fecfcad5e2da2332140bf3b5f23cc1c2a7f4" + integrity sha512-aozo5vqjCmDoXLNUJarFZx2IN/GgGaogY4TMJ6so/WLZOWpSV7fvj2dmrV6sEAnUm1O7aCrhTibjpzeDFgNqbg== "@hapi/address@2.x.x": version "2.0.0" From 74b79e0b1406b21603e1a2465596fc06b29cd53b Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Tue, 22 Apr 2025 13:06:27 +0200 Subject: [PATCH 25/54] fix faker image seed (#8422) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Images now display properly and there is diffent images now allow to provide url, urls msut be valid in seed Co-authored-by: Wolfgang Huß --- backend/package.json | 2 +- backend/src/db/factories.ts | 20 +++++++--- backend/src/db/seed.ts | 39 +++++-------------- .../softDelete/softDeleteMiddleware.spec.ts | 8 ++-- .../schema/resolvers/images/images.spec.ts | 4 +- backend/src/schema/resolvers/posts.spec.ts | 2 +- backend/yarn.lock | 8 ++-- 7 files changed, 36 insertions(+), 47 deletions(-) diff --git a/backend/package.json b/backend/package.json index 7ebdb57b2..60ecba12e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -102,7 +102,7 @@ }, "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "^4.5.0", - "@faker-js/faker": "9.6.0", + "@faker-js/faker": "9.7.0", "@types/jest": "^29.5.14", "@types/node": "^22.14.1", "@typescript-eslint/eslint-plugin": "^5.62.0", diff --git a/backend/src/db/factories.ts b/backend/src/db/factories.ts index 6839ae65c..a0230a467 100644 --- a/backend/src/db/factories.ts +++ b/backend/src/db/factories.ts @@ -12,7 +12,6 @@ import { Factory } from 'rosie' import slugify from 'slug' import { v4 as uuid } from 'uuid' -import CONFIG from '@config/index' import generateInviteCode from '@schema/resolvers/helpers/generateInviteCode' import { getDriver, getNeode } from './neo4j' @@ -20,7 +19,7 @@ import { getDriver, getNeode } from './neo4j' const neode = getNeode() const uniqueImageUrl = (imageUrl) => { - const newUrl = new URL(imageUrl, CONFIG.CLIENT_URI) + const newUrl = new URL(imageUrl) newUrl.search = `random=${uuid()}` return newUrl.toString() } @@ -60,13 +59,22 @@ Factory.define('badge') }) Factory.define('image') - .attr('url', faker.image.url) - .attr('aspectRatio', 1.3333333333333333) + .attr('width', 400) + .attr('height', 300) + .attr('blur', 0) .attr('alt', faker.lorem.sentence) .attr('type', 'image/jpeg') + .attr('url', null) .after((buildObject, _options) => { - const { url: imageUrl } = buildObject - if (imageUrl) buildObject.url = uniqueImageUrl(imageUrl) + if (!buildObject.url) { + buildObject.url = faker.image.urlPicsumPhotos({ + width: buildObject.width, + height: buildObject.height, + blur: buildObject.blur, + }) + } + buildObject.url = uniqueImageUrl(buildObject.url) + buildObject.aspectRatio = buildObject.width / buildObject.height return neode.create('Image', buildObject) }) diff --git a/backend/src/db/seed.ts b/backend/src/db/seed.ts index d0aa5bd29..08594d1b4 100644 --- a/backend/src/db/seed.ts +++ b/backend/src/db/seed.ts @@ -670,9 +670,9 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] categoryIds: ['cat16'], author: peterLustig, image: Factory.build('image', { - url: faker.image.urlLoremFlickr({ category: 'food', width: 300, height: 169 }), + width: 300, + height: 169, sensitive: true, - aspectRatio: 300 / 169, }), }, ) @@ -686,8 +686,8 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] categoryIds: ['cat1'], author: bobDerBaumeister, image: Factory.build('image', { - url: faker.image.urlLoremFlickr({ category: 'technics', width: 300, height: 1500 }), - aspectRatio: 300 / 1500, + width: 300, + height: 1500, }), }, ) @@ -734,8 +734,8 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] categoryIds: ['cat6'], author: peterLustig, image: Factory.build('image', { - url: faker.image.urlLoremFlickr({ category: 'city', width: 300, height: 857 }), - aspectRatio: 300 / 857, + width: 300, + height: 857, }), }, ) @@ -773,8 +773,8 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] categoryIds: ['cat11'], author: louie, image: Factory.build('image', { - url: faker.image.urlLoremFlickr({ category: 'people', width: 300, height: 901 }), - aspectRatio: 300 / 901, + width: 300, + height: 901, }), }, ) @@ -799,8 +799,8 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] categoryIds: ['cat14'], author: jennyRostock, image: Factory.build('image', { - url: faker.image.urlLoremFlickr({ category: 'abstract', width: 300, height: 200 }), - aspectRatio: 300 / 450, + width: 300, + height: 200, }), }, ) @@ -859,7 +859,6 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] mutation: createPostMutation(), variables: { id: 'p8', - image: faker.image.urlLoremFlickr({ category: 'nature' }), title: `Quantum Flow Theory explains Quantum Gravity`, content: hashtagAndMention1, categoryIds: ['cat8'], @@ -1218,9 +1217,6 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] { categoryIds: ['cat1'], author: jennyRostock, - image: Factory.build('image', { - url: faker.image.urlLoremFlickr({ category: 'abstract' }), - }), }, ) } @@ -1269,9 +1265,6 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] { categoryIds: ['cat1'], author: peterLustig, - image: Factory.build('image', { - url: faker.image.urlLoremFlickr({ category: 'city' }), - }), }, ) } @@ -1320,9 +1313,6 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] { categoryIds: ['cat1'], author: dewey, - image: Factory.build('image', { - url: faker.image.urlLoremFlickr({ category: 'food' }), - }), }, ) } @@ -1371,9 +1361,6 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] { categoryIds: ['cat1'], author: louie, - image: Factory.build('image', { - url: faker.image.urlLoremFlickr({ category: 'technics' }), - }), }, ) } @@ -1422,9 +1409,6 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] { categoryIds: ['cat1'], author: bobDerBaumeister, - image: Factory.build('image', { - url: faker.image.urlLoremFlickr({ category: 'people' }), - }), }, ) } @@ -1473,9 +1457,6 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] { categoryIds: ['cat1'], author: huey, - image: Factory.build('image', { - url: faker.image.urlLoremFlickr({ category: 'nature' }), - }), }, ) } diff --git a/backend/src/middleware/softDelete/softDeleteMiddleware.spec.ts b/backend/src/middleware/softDelete/softDeleteMiddleware.spec.ts index 6a718fdde..0264dedb9 100644 --- a/backend/src/middleware/softDelete/softDeleteMiddleware.spec.ts +++ b/backend/src/middleware/softDelete/softDeleteMiddleware.spec.ts @@ -48,7 +48,7 @@ beforeAll(async () => { }, { avatar: Factory.build('image', { - url: '/some/offensive/avatar.jpg', + url: 'http://localhost/some/offensive/avatar.jpg', }), }, ), @@ -116,7 +116,7 @@ beforeAll(async () => { }, { image: Factory.build('image', { - url: '/some/offensive/image.jpg', + url: 'http://localhost/some/offensive/image.jpg', }), author: troll, categoryIds, @@ -278,7 +278,7 @@ describe('softDeleteMiddleware', () => { expect(subject.about).toEqual('This self description is very offensive')) it('displays avatar', () => expect(subject.avatar).toEqual({ - url: expect.stringContaining('/some/offensive/avatar.jpg'), + url: expect.stringMatching('http://localhost/some/offensive/avatar.jpg'), })) }) @@ -293,7 +293,7 @@ describe('softDeleteMiddleware', () => { expect(subject.contentExcerpt).toEqual('This is an offensive post content')) it('displays image', () => expect(subject.image).toEqual({ - url: expect.stringContaining('/some/offensive/image.jpg'), + url: expect.stringMatching('http://localhost/some/offensive/image.jpg'), })) }) diff --git a/backend/src/schema/resolvers/images/images.spec.ts b/backend/src/schema/resolvers/images/images.spec.ts index 8f8cebf36..0a2cbadbd 100644 --- a/backend/src/schema/resolvers/images/images.spec.ts +++ b/backend/src/schema/resolvers/images/images.spec.ts @@ -47,7 +47,7 @@ describe('deleteImage', () => { {}, { avatar: Factory.build('image', { - url: '/some/avatar/url/', + url: 'http://localhost/some/avatar/url/', alt: 'This is the avatar image of a user', }), }, @@ -336,7 +336,7 @@ describe('mergeImage', () => { ), image: Factory.build('image', { alt: 'This is the previous, not updated image', - url: '/some/original/url', + url: 'http://localhost/some/original/url', }), }, ) diff --git a/backend/src/schema/resolvers/posts.spec.ts b/backend/src/schema/resolvers/posts.spec.ts index 108ff51c5..403db3950 100644 --- a/backend/src/schema/resolvers/posts.spec.ts +++ b/backend/src/schema/resolvers/posts.spec.ts @@ -1468,7 +1468,7 @@ describe('DeletePost', () => { }, { image: Factory.build('image', { - url: 'path/to/some/image', + url: 'http://localhost/path/to/some/image', }), author, categoryIds, diff --git a/backend/yarn.lock b/backend/yarn.lock index 3bed8038a..3f0aa3990 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -1143,10 +1143,10 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== -"@faker-js/faker@9.6.0": - version "9.6.0" - resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.6.0.tgz#64235d20330b142eef3d1d1638ba56c083b4bf1d" - integrity sha512-3vm4by+B5lvsFPSyep3ELWmZfE3kicDtmemVpuwl1yH7tqtnHdsA6hG8fbXedMVdkzgtvzWoRgjSB4Q+FHnZiw== +"@faker-js/faker@9.7.0": + version "9.7.0" + resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.7.0.tgz#1cf1fecfcad5e2da2332140bf3b5f23cc1c2a7f4" + integrity sha512-aozo5vqjCmDoXLNUJarFZx2IN/GgGaogY4TMJ6so/WLZOWpSV7fvj2dmrV6sEAnUm1O7aCrhTibjpzeDFgNqbg== "@graphql-toolkit/common@0.10.4": version "0.10.4" From 7592fe29beb5f4e5a8414801ebf94427b39e49cb Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Tue, 22 Apr 2025 15:28:01 +0200 Subject: [PATCH 26/54] move graphql types into graphql folder (#8420) --- backend/scripts/build.copy.files.sh | 16 ++++++++-------- .../types/enum/EmailNotificationSettingsName.gql | 0 .../types/enum/EmailNotificationSettingsType.gql | 0 .../{schema => graphql}/types/enum/Emotion.gql | 0 .../types/enum/GroupActionRadius.gql | 0 .../types/enum/GroupMemberRole.gql | 0 .../{schema => graphql}/types/enum/GroupType.gql | 0 .../types/enum/OnlineStatus.gql | 0 .../{schema => graphql}/types/enum/PostType.gql | 0 backend/src/graphql/types/enum/ShoutTypeEnum.gql | 3 +++ .../{schema => graphql}/types/enum/UserRole.gql | 0 .../types/enum/Visibility.gql | 0 backend/src/{schema => graphql}/types/index.ts | 0 .../{schema => graphql}/types/scalar/Upload.gql | 0 .../src/{schema => graphql}/types/type/Badge.gql | 0 .../{schema => graphql}/types/type/Category.gql | 0 .../{schema => graphql}/types/type/Comment.gql | 0 .../{schema => graphql}/types/type/Donations.gql | 0 .../{schema => graphql}/types/type/EMOTED.gql | 0 .../types/type/EmailAddress.gql | 0 .../embed.gql => graphql/types/type/Embed.gql} | 0 .../src/{schema => graphql}/types/type/FILED.gql | 0 .../src/{schema => graphql}/types/type/Group.gql | 0 .../src/{schema => graphql}/types/type/Image.gql | 0 .../types/type/InviteCode.gql | 0 .../{schema => graphql}/types/type/Location.gql | 0 .../{schema => graphql}/types/type/MEMBER_OF.gql | 0 .../{schema => graphql}/types/type/Message.gql | 0 .../{schema => graphql}/types/type/NOTIFIED.gql | 0 .../src/{schema => graphql}/types/type/Post.gql | 0 .../{schema => graphql}/types/type/REVIEWED.gql | 0 .../{schema => graphql}/types/type/Report.gql | 0 .../types/type/Reward.gql.unused} | 9 --------- .../src/{schema => graphql}/types/type/Room.gql | 0 .../{schema => graphql}/types/type/Search.gql | 0 .../types/type/SharedInboxEndpoint.gql.old | 4 ++++ .../types/type/SocialMedia.gql | 0 .../types/type/Statistics.gql | 0 .../src/{schema => graphql}/types/type/Tag.gql | 0 .../src/{schema => graphql}/types/type/User.gql | 0 .../{schema => graphql}/types/type/UserData.gql | 0 backend/src/schema/index.ts | 3 ++- 42 files changed, 17 insertions(+), 18 deletions(-) rename backend/src/{schema => graphql}/types/enum/EmailNotificationSettingsName.gql (100%) rename backend/src/{schema => graphql}/types/enum/EmailNotificationSettingsType.gql (100%) rename backend/src/{schema => graphql}/types/enum/Emotion.gql (100%) rename backend/src/{schema => graphql}/types/enum/GroupActionRadius.gql (100%) rename backend/src/{schema => graphql}/types/enum/GroupMemberRole.gql (100%) rename backend/src/{schema => graphql}/types/enum/GroupType.gql (100%) rename backend/src/{schema => graphql}/types/enum/OnlineStatus.gql (100%) rename backend/src/{schema => graphql}/types/enum/PostType.gql (100%) create mode 100644 backend/src/graphql/types/enum/ShoutTypeEnum.gql rename backend/src/{schema => graphql}/types/enum/UserRole.gql (100%) rename backend/src/{schema => graphql}/types/enum/Visibility.gql (100%) rename backend/src/{schema => graphql}/types/index.ts (100%) rename backend/src/{schema => graphql}/types/scalar/Upload.gql (100%) rename backend/src/{schema => graphql}/types/type/Badge.gql (100%) rename backend/src/{schema => graphql}/types/type/Category.gql (100%) rename backend/src/{schema => graphql}/types/type/Comment.gql (100%) rename backend/src/{schema => graphql}/types/type/Donations.gql (100%) rename backend/src/{schema => graphql}/types/type/EMOTED.gql (100%) rename backend/src/{schema => graphql}/types/type/EmailAddress.gql (100%) rename backend/src/{schema/types/embed.gql => graphql/types/type/Embed.gql} (100%) rename backend/src/{schema => graphql}/types/type/FILED.gql (100%) rename backend/src/{schema => graphql}/types/type/Group.gql (100%) rename backend/src/{schema => graphql}/types/type/Image.gql (100%) rename backend/src/{schema => graphql}/types/type/InviteCode.gql (100%) rename backend/src/{schema => graphql}/types/type/Location.gql (100%) rename backend/src/{schema => graphql}/types/type/MEMBER_OF.gql (100%) rename backend/src/{schema => graphql}/types/type/Message.gql (100%) rename backend/src/{schema => graphql}/types/type/NOTIFIED.gql (100%) rename backend/src/{schema => graphql}/types/type/Post.gql (100%) rename backend/src/{schema => graphql}/types/type/REVIEWED.gql (100%) rename backend/src/{schema => graphql}/types/type/Report.gql (100%) rename backend/src/{schema/types/schema.gql => graphql/types/type/Reward.gql.unused} (68%) rename backend/src/{schema => graphql}/types/type/Room.gql (100%) rename backend/src/{schema => graphql}/types/type/Search.gql (100%) create mode 100644 backend/src/graphql/types/type/SharedInboxEndpoint.gql.old rename backend/src/{schema => graphql}/types/type/SocialMedia.gql (100%) rename backend/src/{schema => graphql}/types/type/Statistics.gql (100%) rename backend/src/{schema => graphql}/types/type/Tag.gql (100%) rename backend/src/{schema => graphql}/types/type/User.gql (100%) rename backend/src/{schema => graphql}/types/type/UserData.gql (100%) diff --git a/backend/scripts/build.copy.files.sh b/backend/scripts/build.copy.files.sh index da76a623c..7279291d6 100755 --- a/backend/scripts/build.copy.files.sh +++ b/backend/scripts/build.copy.files.sh @@ -14,14 +14,14 @@ mkdir -p build/src/middleware/helpers/email/templates/de/ cp -r src/middleware/helpers/email/templates/de/*.html build/src/middleware/helpers/email/templates/de/ # gql files -mkdir -p build/src/schema/types/ -cp -r src/schema/types/*.gql build/src/schema/types/ +mkdir -p build/src/graphql/types/ +cp -r src/graphql/types/*.gql build/src/graphql/types/ -mkdir -p build/src/schema/types/enum/ -cp -r src/schema/types/enum/*.gql build/src/schema/types/enum/ +mkdir -p build/src/graphql/types/enum/ +cp -r src/graphql/types/enum/*.gql build/src/graphql/types/enum/ -mkdir -p build/src/schema/types/scalar/ -cp -r src/schema/types/scalar/*.gql build/src/schema/types/scalar/ +mkdir -p build/src/graphql/types/scalar/ +cp -r src/graphql/types/scalar/*.gql build/src/graphql/types/scalar/ -mkdir -p build/src/schema/types/type/ -cp -r src/schema/types/type/*.gql build/src/schema/types/type/ \ No newline at end of file +mkdir -p build/src/graphql/types/type/ +cp -r src/graphql/types/type/*.gql build/src/graphql/types/type/ \ No newline at end of file diff --git a/backend/src/schema/types/enum/EmailNotificationSettingsName.gql b/backend/src/graphql/types/enum/EmailNotificationSettingsName.gql similarity index 100% rename from backend/src/schema/types/enum/EmailNotificationSettingsName.gql rename to backend/src/graphql/types/enum/EmailNotificationSettingsName.gql diff --git a/backend/src/schema/types/enum/EmailNotificationSettingsType.gql b/backend/src/graphql/types/enum/EmailNotificationSettingsType.gql similarity index 100% rename from backend/src/schema/types/enum/EmailNotificationSettingsType.gql rename to backend/src/graphql/types/enum/EmailNotificationSettingsType.gql diff --git a/backend/src/schema/types/enum/Emotion.gql b/backend/src/graphql/types/enum/Emotion.gql similarity index 100% rename from backend/src/schema/types/enum/Emotion.gql rename to backend/src/graphql/types/enum/Emotion.gql diff --git a/backend/src/schema/types/enum/GroupActionRadius.gql b/backend/src/graphql/types/enum/GroupActionRadius.gql similarity index 100% rename from backend/src/schema/types/enum/GroupActionRadius.gql rename to backend/src/graphql/types/enum/GroupActionRadius.gql diff --git a/backend/src/schema/types/enum/GroupMemberRole.gql b/backend/src/graphql/types/enum/GroupMemberRole.gql similarity index 100% rename from backend/src/schema/types/enum/GroupMemberRole.gql rename to backend/src/graphql/types/enum/GroupMemberRole.gql diff --git a/backend/src/schema/types/enum/GroupType.gql b/backend/src/graphql/types/enum/GroupType.gql similarity index 100% rename from backend/src/schema/types/enum/GroupType.gql rename to backend/src/graphql/types/enum/GroupType.gql diff --git a/backend/src/schema/types/enum/OnlineStatus.gql b/backend/src/graphql/types/enum/OnlineStatus.gql similarity index 100% rename from backend/src/schema/types/enum/OnlineStatus.gql rename to backend/src/graphql/types/enum/OnlineStatus.gql diff --git a/backend/src/schema/types/enum/PostType.gql b/backend/src/graphql/types/enum/PostType.gql similarity index 100% rename from backend/src/schema/types/enum/PostType.gql rename to backend/src/graphql/types/enum/PostType.gql diff --git a/backend/src/graphql/types/enum/ShoutTypeEnum.gql b/backend/src/graphql/types/enum/ShoutTypeEnum.gql new file mode 100644 index 000000000..87fcbc5ff --- /dev/null +++ b/backend/src/graphql/types/enum/ShoutTypeEnum.gql @@ -0,0 +1,3 @@ +enum ShoutTypeEnum { + Post +} \ No newline at end of file diff --git a/backend/src/schema/types/enum/UserRole.gql b/backend/src/graphql/types/enum/UserRole.gql similarity index 100% rename from backend/src/schema/types/enum/UserRole.gql rename to backend/src/graphql/types/enum/UserRole.gql diff --git a/backend/src/schema/types/enum/Visibility.gql b/backend/src/graphql/types/enum/Visibility.gql similarity index 100% rename from backend/src/schema/types/enum/Visibility.gql rename to backend/src/graphql/types/enum/Visibility.gql diff --git a/backend/src/schema/types/index.ts b/backend/src/graphql/types/index.ts similarity index 100% rename from backend/src/schema/types/index.ts rename to backend/src/graphql/types/index.ts diff --git a/backend/src/schema/types/scalar/Upload.gql b/backend/src/graphql/types/scalar/Upload.gql similarity index 100% rename from backend/src/schema/types/scalar/Upload.gql rename to backend/src/graphql/types/scalar/Upload.gql diff --git a/backend/src/schema/types/type/Badge.gql b/backend/src/graphql/types/type/Badge.gql similarity index 100% rename from backend/src/schema/types/type/Badge.gql rename to backend/src/graphql/types/type/Badge.gql diff --git a/backend/src/schema/types/type/Category.gql b/backend/src/graphql/types/type/Category.gql similarity index 100% rename from backend/src/schema/types/type/Category.gql rename to backend/src/graphql/types/type/Category.gql diff --git a/backend/src/schema/types/type/Comment.gql b/backend/src/graphql/types/type/Comment.gql similarity index 100% rename from backend/src/schema/types/type/Comment.gql rename to backend/src/graphql/types/type/Comment.gql diff --git a/backend/src/schema/types/type/Donations.gql b/backend/src/graphql/types/type/Donations.gql similarity index 100% rename from backend/src/schema/types/type/Donations.gql rename to backend/src/graphql/types/type/Donations.gql diff --git a/backend/src/schema/types/type/EMOTED.gql b/backend/src/graphql/types/type/EMOTED.gql similarity index 100% rename from backend/src/schema/types/type/EMOTED.gql rename to backend/src/graphql/types/type/EMOTED.gql diff --git a/backend/src/schema/types/type/EmailAddress.gql b/backend/src/graphql/types/type/EmailAddress.gql similarity index 100% rename from backend/src/schema/types/type/EmailAddress.gql rename to backend/src/graphql/types/type/EmailAddress.gql diff --git a/backend/src/schema/types/embed.gql b/backend/src/graphql/types/type/Embed.gql similarity index 100% rename from backend/src/schema/types/embed.gql rename to backend/src/graphql/types/type/Embed.gql diff --git a/backend/src/schema/types/type/FILED.gql b/backend/src/graphql/types/type/FILED.gql similarity index 100% rename from backend/src/schema/types/type/FILED.gql rename to backend/src/graphql/types/type/FILED.gql diff --git a/backend/src/schema/types/type/Group.gql b/backend/src/graphql/types/type/Group.gql similarity index 100% rename from backend/src/schema/types/type/Group.gql rename to backend/src/graphql/types/type/Group.gql diff --git a/backend/src/schema/types/type/Image.gql b/backend/src/graphql/types/type/Image.gql similarity index 100% rename from backend/src/schema/types/type/Image.gql rename to backend/src/graphql/types/type/Image.gql diff --git a/backend/src/schema/types/type/InviteCode.gql b/backend/src/graphql/types/type/InviteCode.gql similarity index 100% rename from backend/src/schema/types/type/InviteCode.gql rename to backend/src/graphql/types/type/InviteCode.gql diff --git a/backend/src/schema/types/type/Location.gql b/backend/src/graphql/types/type/Location.gql similarity index 100% rename from backend/src/schema/types/type/Location.gql rename to backend/src/graphql/types/type/Location.gql diff --git a/backend/src/schema/types/type/MEMBER_OF.gql b/backend/src/graphql/types/type/MEMBER_OF.gql similarity index 100% rename from backend/src/schema/types/type/MEMBER_OF.gql rename to backend/src/graphql/types/type/MEMBER_OF.gql diff --git a/backend/src/schema/types/type/Message.gql b/backend/src/graphql/types/type/Message.gql similarity index 100% rename from backend/src/schema/types/type/Message.gql rename to backend/src/graphql/types/type/Message.gql diff --git a/backend/src/schema/types/type/NOTIFIED.gql b/backend/src/graphql/types/type/NOTIFIED.gql similarity index 100% rename from backend/src/schema/types/type/NOTIFIED.gql rename to backend/src/graphql/types/type/NOTIFIED.gql diff --git a/backend/src/schema/types/type/Post.gql b/backend/src/graphql/types/type/Post.gql similarity index 100% rename from backend/src/schema/types/type/Post.gql rename to backend/src/graphql/types/type/Post.gql diff --git a/backend/src/schema/types/type/REVIEWED.gql b/backend/src/graphql/types/type/REVIEWED.gql similarity index 100% rename from backend/src/schema/types/type/REVIEWED.gql rename to backend/src/graphql/types/type/REVIEWED.gql diff --git a/backend/src/schema/types/type/Report.gql b/backend/src/graphql/types/type/Report.gql similarity index 100% rename from backend/src/schema/types/type/Report.gql rename to backend/src/graphql/types/type/Report.gql diff --git a/backend/src/schema/types/schema.gql b/backend/src/graphql/types/type/Reward.gql.unused similarity index 68% rename from backend/src/schema/types/schema.gql rename to backend/src/graphql/types/type/Reward.gql.unused index 9e2d00bca..6691b34f1 100644 --- a/backend/src/schema/types/schema.gql +++ b/backend/src/graphql/types/type/Reward.gql.unused @@ -1,16 +1,7 @@ -enum ShoutTypeEnum { - Post -} - type Reward { id: ID! user: User @relation(name: "REWARDED", direction: "IN") rewarderId: ID createdAt: String badge: Badge @relation(name: "REWARDED", direction: "OUT") -} - -type SharedInboxEndpoint { - id: ID! - uri: String } \ No newline at end of file diff --git a/backend/src/schema/types/type/Room.gql b/backend/src/graphql/types/type/Room.gql similarity index 100% rename from backend/src/schema/types/type/Room.gql rename to backend/src/graphql/types/type/Room.gql diff --git a/backend/src/schema/types/type/Search.gql b/backend/src/graphql/types/type/Search.gql similarity index 100% rename from backend/src/schema/types/type/Search.gql rename to backend/src/graphql/types/type/Search.gql diff --git a/backend/src/graphql/types/type/SharedInboxEndpoint.gql.old b/backend/src/graphql/types/type/SharedInboxEndpoint.gql.old new file mode 100644 index 000000000..b078af63b --- /dev/null +++ b/backend/src/graphql/types/type/SharedInboxEndpoint.gql.old @@ -0,0 +1,4 @@ +type SharedInboxEndpoint { + id: ID! + uri: String +} \ No newline at end of file diff --git a/backend/src/schema/types/type/SocialMedia.gql b/backend/src/graphql/types/type/SocialMedia.gql similarity index 100% rename from backend/src/schema/types/type/SocialMedia.gql rename to backend/src/graphql/types/type/SocialMedia.gql diff --git a/backend/src/schema/types/type/Statistics.gql b/backend/src/graphql/types/type/Statistics.gql similarity index 100% rename from backend/src/schema/types/type/Statistics.gql rename to backend/src/graphql/types/type/Statistics.gql diff --git a/backend/src/schema/types/type/Tag.gql b/backend/src/graphql/types/type/Tag.gql similarity index 100% rename from backend/src/schema/types/type/Tag.gql rename to backend/src/graphql/types/type/Tag.gql diff --git a/backend/src/schema/types/type/User.gql b/backend/src/graphql/types/type/User.gql similarity index 100% rename from backend/src/schema/types/type/User.gql rename to backend/src/graphql/types/type/User.gql diff --git a/backend/src/schema/types/type/UserData.gql b/backend/src/graphql/types/type/UserData.gql similarity index 100% rename from backend/src/schema/types/type/UserData.gql rename to backend/src/graphql/types/type/UserData.gql diff --git a/backend/src/schema/index.ts b/backend/src/schema/index.ts index 702b4a001..55eccb4dc 100644 --- a/backend/src/schema/index.ts +++ b/backend/src/schema/index.ts @@ -2,8 +2,9 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { makeAugmentedSchema } from 'neo4j-graphql-js' +import typeDefs from '@graphql/types/index' + import resolvers from './resolvers' -import typeDefs from './types' export default makeAugmentedSchema({ typeDefs, From c090db386675bfa373afe9122eb47d5a503a0972 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 22 Apr 2025 19:28:51 +0200 Subject: [PATCH 27/54] feat(webapp): badges admin settings (#8401) Adds a link to badges settings in the user table, where admins can set the available badges. --- .../Admin/Badges/BadgesSection.spec.js | 46 +++ .../features/Admin/Badges/BadgesSection.vue | 55 +++ .../__snapshots__/BadgesSection.spec.js.snap | 36 ++ webapp/graphql/User.js | 17 + webapp/graphql/admin/Badges.js | 54 +++ webapp/locales/de.json | 26 +- webapp/locales/en.json | 26 +- webapp/locales/es.json | 26 +- webapp/locales/fr.json | 26 +- webapp/locales/it.json | 26 +- webapp/locales/nl.json | 26 +- webapp/locales/pl.json | 26 +- webapp/locales/pt.json | 26 +- webapp/locales/ru.json | 26 +- webapp/nuxt.config.js | 9 + .../users/__snapshots__/_id.spec.js.snap | 104 ++++++ webapp/pages/admin/users/_id.spec.js | 326 ++++++++++++++++++ webapp/pages/admin/users/_id.vue | 163 +++++++++ .../{users.spec.js => users/index.spec.js} | 2 +- .../admin/{users.vue => users/index.vue} | 14 + 20 files changed, 1050 insertions(+), 10 deletions(-) create mode 100644 webapp/components/_new/features/Admin/Badges/BadgesSection.spec.js create mode 100644 webapp/components/_new/features/Admin/Badges/BadgesSection.vue create mode 100644 webapp/components/_new/features/Admin/Badges/__snapshots__/BadgesSection.spec.js.snap create mode 100644 webapp/graphql/admin/Badges.js create mode 100644 webapp/pages/admin/users/__snapshots__/_id.spec.js.snap create mode 100644 webapp/pages/admin/users/_id.spec.js create mode 100644 webapp/pages/admin/users/_id.vue rename webapp/pages/admin/{users.spec.js => users/index.spec.js} (99%) rename webapp/pages/admin/{users.vue => users/index.vue} (93%) diff --git a/webapp/components/_new/features/Admin/Badges/BadgesSection.spec.js b/webapp/components/_new/features/Admin/Badges/BadgesSection.spec.js new file mode 100644 index 000000000..8baddc692 --- /dev/null +++ b/webapp/components/_new/features/Admin/Badges/BadgesSection.spec.js @@ -0,0 +1,46 @@ +import { render, fireEvent, screen } from '@testing-library/vue' +import BadgesSection from './BadgesSection.vue' + +const localVue = global.localVue + +const badge1 = { + id: 'badge1', + icon: 'icon1', + type: 'type1', + description: 'description1', + isActive: true, +} +const badge2 = { + id: 'badge2', + icon: 'icon2', + type: 'type1', + description: 'description2', + isActive: false, +} + +describe('Admin/BadgesSection', () => { + let wrapper + + const Wrapper = () => { + return render(BadgesSection, { + localVue, + propsData: { + badges: [badge1, badge2], + }, + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.baseElement).toMatchSnapshot() + }) + + it('emits toggleButton', async () => { + const button = screen.getByAltText(badge1.description) + await fireEvent.click(button) + expect(wrapper.emitted().toggleBadge[0][0]).toEqual(badge1) + }) +}) diff --git a/webapp/components/_new/features/Admin/Badges/BadgesSection.vue b/webapp/components/_new/features/Admin/Badges/BadgesSection.vue new file mode 100644 index 000000000..8ff9da7ed --- /dev/null +++ b/webapp/components/_new/features/Admin/Badges/BadgesSection.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/webapp/components/_new/features/Admin/Badges/__snapshots__/BadgesSection.spec.js.snap b/webapp/components/_new/features/Admin/Badges/__snapshots__/BadgesSection.spec.js.snap new file mode 100644 index 000000000..c09a50725 --- /dev/null +++ b/webapp/components/_new/features/Admin/Badges/__snapshots__/BadgesSection.spec.js.snap @@ -0,0 +1,36 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Admin/BadgesSection renders 1`] = ` + +
+
+

+ +

+ +
+ + +
+
+
+ +`; diff --git a/webapp/graphql/User.js b/webapp/graphql/User.js index 8ad247ad1..147e93c6f 100644 --- a/webapp/graphql/User.js +++ b/webapp/graphql/User.js @@ -90,6 +90,23 @@ export const adminUserQuery = () => { ` } +export const adminUserBadgesQuery = () => { + return gql` + query User($id: ID!) { + User(id: $id) { + id + name + badgeTrophies { + id + } + badgeVerification { + id + } + } + } + ` +} + export const mapUserQuery = (i18n) => { const lang = i18n.locale().toUpperCase() return gql` diff --git a/webapp/graphql/admin/Badges.js b/webapp/graphql/admin/Badges.js new file mode 100644 index 000000000..2c037f2f3 --- /dev/null +++ b/webapp/graphql/admin/Badges.js @@ -0,0 +1,54 @@ +import gql from 'graphql-tag' + +export const queryBadges = () => gql` + query { + Badge { + id + type + icon + description + } + } +` + +export const setVerificationBadge = () => gql` + mutation ($badgeId: ID!, $userId: ID!) { + setVerificationBadge(badgeId: $badgeId, userId: $userId) { + id + badgeVerification { + id + } + badgeTrophies { + id + } + } + } +` + +export const rewardTrophyBadge = () => gql` + mutation ($badgeId: ID!, $userId: ID!) { + rewardTrophyBadge(badgeId: $badgeId, userId: $userId) { + id + badgeVerification { + id + } + badgeTrophies { + id + } + } + } +` + +export const revokeBadge = () => gql` + mutation ($badgeId: ID!, $userId: ID!) { + revokeBadge(badgeId: $badgeId, userId: $userId) { + id + badgeVerification { + id + } + badgeTrophies { + id + } + } + } +` diff --git a/webapp/locales/de.json b/webapp/locales/de.json index 19d0896a9..ce122672d 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -10,6 +10,28 @@ "saveCategories": "Themen speichern" }, "admin": { + "badges": { + "description": "Stelle die verfügbaren Auszeichnungen für diesen Nutzer ein.", + "revokeTrophy": { + "error": "Trophäe konnte nicht widerrufen werden!", + "success": "Trophäe erfolgreich widerrufen" + }, + "revokeVerification": { + "error": "Verifizierung konnte nicht gesetzt werden!", + "success": "Verifizierung erfolgreich widerrufen" + }, + "rewardTrophy": { + "error": "Trophäe konnte nicht vergeben werden!", + "success": "Trophäe erfolgreich vergeben!" + }, + "setVerification": { + "error": "Verifizierung konnte nicht gesetzt werden!", + "success": "Verifizierung erfolgreich gesetzt" + }, + "title": "Auszeichnungen", + "trophyBadges": "Trophäen", + "verificationBadges": "Verifizierungen" + }, "categories": { "categoryName": "Name", "name": "Themen", @@ -68,13 +90,15 @@ "roleChanged": "Rolle erfolgreich geändert!", "table": { "columns": { + "badges": "Auszeichnungen", "createdAt": "Erstellt am", "email": "E-Mail", "name": "Name", "number": "Nr.", "role": "Rolle", "slug": "Alias" - } + }, + "edit": "Bearbeiten" } } }, diff --git a/webapp/locales/en.json b/webapp/locales/en.json index b4c1125f3..f178da549 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -10,6 +10,28 @@ "saveCategories": "Save topics" }, "admin": { + "badges": { + "description": "Configure the available badges for this user", + "revokeTrophy": { + "error": "Trophy could not be revoked!", + "success": "Trophy successfully revoked!" + }, + "revokeVerification": { + "error": "Verification could not be revoked!", + "success": "Verification succesfully revoked" + }, + "rewardTrophy": { + "error": "Trophy could not be rewarded!", + "success": "Trophy successfully rewarded!" + }, + "setVerification": { + "error": "Verification could not be set!", + "success": "Verification successfully set!" + }, + "title": "Badges", + "trophyBadges": "Trophies", + "verificationBadges": "Verifications" + }, "categories": { "categoryName": "Name", "name": "Topics", @@ -68,13 +90,15 @@ "roleChanged": "Role changed successfully!", "table": { "columns": { + "badges": "Badges", "createdAt": "Created at", "email": "E-mail", "name": "Name", "number": "No.", "role": "Role", "slug": "Slug" - } + }, + "edit": "Edit" } } }, diff --git a/webapp/locales/es.json b/webapp/locales/es.json index 7184a327a..31f2cc5f4 100644 --- a/webapp/locales/es.json +++ b/webapp/locales/es.json @@ -10,6 +10,28 @@ "saveCategories": null }, "admin": { + "badges": { + "description": null, + "revokeTrophy": { + "error": null, + "success": null + }, + "revokeVerification": { + "error": null, + "success": null + }, + "rewardTrophy": { + "error": null, + "success": null + }, + "setVerification": { + "error": null, + "success": null + }, + "title": null, + "trophyBadges": null, + "verificationBadges": null + }, "categories": { "categoryName": "Nombre", "name": "Categorías", @@ -68,13 +90,15 @@ "roleChanged": null, "table": { "columns": { + "badges": null, "createdAt": "Creado el", "email": "Correo electrónico", "name": "Nombre", "number": "No.", "role": "Rol", "slug": "Alias" - } + }, + "edit": null } } }, diff --git a/webapp/locales/fr.json b/webapp/locales/fr.json index 851743e63..4bbca2b82 100644 --- a/webapp/locales/fr.json +++ b/webapp/locales/fr.json @@ -10,6 +10,28 @@ "saveCategories": null }, "admin": { + "badges": { + "description": null, + "revokeTrophy": { + "error": null, + "success": null + }, + "revokeVerification": { + "error": null, + "success": null + }, + "rewardTrophy": { + "error": null, + "success": null + }, + "setVerification": { + "error": null, + "success": null + }, + "title": null, + "trophyBadges": null, + "verificationBadges": null + }, "categories": { "categoryName": "Nom", "name": "Catégories", @@ -68,13 +90,15 @@ "roleChanged": null, "table": { "columns": { + "badges": null, "createdAt": "Créé à", "email": "Mail", "name": "Nom", "number": "Num.", "role": "Rôle", "slug": "Slug" - } + }, + "edit": null } } }, diff --git a/webapp/locales/it.json b/webapp/locales/it.json index 0c693ca43..21bfaa859 100644 --- a/webapp/locales/it.json +++ b/webapp/locales/it.json @@ -10,6 +10,28 @@ "saveCategories": null }, "admin": { + "badges": { + "description": null, + "revokeTrophy": { + "error": null, + "success": null + }, + "revokeVerification": { + "error": null, + "success": null + }, + "rewardTrophy": { + "error": null, + "success": null + }, + "setVerification": { + "error": null, + "success": null + }, + "title": null, + "trophyBadges": null, + "verificationBadges": null + }, "categories": { "categoryName": "Nome", "name": "Categorie", @@ -68,13 +90,15 @@ "roleChanged": null, "table": { "columns": { + "badges": null, "createdAt": null, "email": null, "name": null, "number": null, "role": null, "slug": null - } + }, + "edit": null } } }, diff --git a/webapp/locales/nl.json b/webapp/locales/nl.json index 433adf8e8..f67518c21 100644 --- a/webapp/locales/nl.json +++ b/webapp/locales/nl.json @@ -10,6 +10,28 @@ "saveCategories": null }, "admin": { + "badges": { + "description": null, + "revokeTrophy": { + "error": null, + "success": null + }, + "revokeVerification": { + "error": null, + "success": null + }, + "rewardTrophy": { + "error": null, + "success": null + }, + "setVerification": { + "error": null, + "success": null + }, + "title": null, + "trophyBadges": null, + "verificationBadges": null + }, "categories": { "categoryName": "Naam", "name": "Categorieën", @@ -68,13 +90,15 @@ "roleChanged": null, "table": { "columns": { + "badges": null, "createdAt": null, "email": null, "name": null, "number": null, "role": null, "slug": null - } + }, + "edit": null } } }, diff --git a/webapp/locales/pl.json b/webapp/locales/pl.json index c0ab9d09c..4c6a96a5f 100644 --- a/webapp/locales/pl.json +++ b/webapp/locales/pl.json @@ -10,6 +10,28 @@ "saveCategories": null }, "admin": { + "badges": { + "description": null, + "revokeTrophy": { + "error": null, + "success": null + }, + "revokeVerification": { + "error": null, + "success": null + }, + "rewardTrophy": { + "error": null, + "success": null + }, + "setVerification": { + "error": null, + "success": null + }, + "title": null, + "trophyBadges": null, + "verificationBadges": null + }, "categories": { "categoryName": "Nazwa", "name": "Kategorie", @@ -68,13 +90,15 @@ "roleChanged": null, "table": { "columns": { + "badges": null, "createdAt": null, "email": null, "name": null, "number": null, "role": null, "slug": null - } + }, + "edit": null } } }, diff --git a/webapp/locales/pt.json b/webapp/locales/pt.json index 02f8fb2cc..7d5ad52c1 100644 --- a/webapp/locales/pt.json +++ b/webapp/locales/pt.json @@ -10,6 +10,28 @@ "saveCategories": null }, "admin": { + "badges": { + "description": null, + "revokeTrophy": { + "error": null, + "success": null + }, + "revokeVerification": { + "error": null, + "success": null + }, + "rewardTrophy": { + "error": null, + "success": null + }, + "setVerification": { + "error": null, + "success": null + }, + "title": null, + "trophyBadges": null, + "verificationBadges": null + }, "categories": { "categoryName": "Nome", "name": "Categorias", @@ -68,13 +90,15 @@ "roleChanged": null, "table": { "columns": { + "badges": null, "createdAt": "Criado em", "email": "E-mail", "name": "Nome", "number": "N.º", "role": "Função", "slug": "Slug" - } + }, + "edit": null } } }, diff --git a/webapp/locales/ru.json b/webapp/locales/ru.json index ea0279450..3a394d6ff 100644 --- a/webapp/locales/ru.json +++ b/webapp/locales/ru.json @@ -10,6 +10,28 @@ "saveCategories": null }, "admin": { + "badges": { + "description": null, + "revokeTrophy": { + "error": null, + "success": null + }, + "revokeVerification": { + "error": null, + "success": null + }, + "rewardTrophy": { + "error": null, + "success": null + }, + "setVerification": { + "error": null, + "success": null + }, + "title": null, + "trophyBadges": null, + "verificationBadges": null + }, "categories": { "categoryName": "Имя", "name": "Категории", @@ -68,13 +90,15 @@ "roleChanged": null, "table": { "columns": { + "badges": null, "createdAt": "Дата создания", "email": "Эл. почта", "name": "Имя", "number": "№", "role": "Роль", "slug": "Алиас" - } + }, + "edit": null } } }, diff --git a/webapp/nuxt.config.js b/webapp/nuxt.config.js index b3bbdfc2d..1c963615a 100644 --- a/webapp/nuxt.config.js +++ b/webapp/nuxt.config.js @@ -207,6 +207,15 @@ export default { 'X-API-TOKEN': CONFIG.BACKEND_TOKEN, }, }, + '/img': { + // make this configurable (nuxt-dotenv) + target: CONFIG.GRAPHQL_URI, + toProxy: true, // cloudflare needs that + headers: { + 'X-UI-Request': true, + 'X-API-TOKEN': CONFIG.BACKEND_TOKEN, + }, + }, }, // Give apollo module options diff --git a/webapp/pages/admin/users/__snapshots__/_id.spec.js.snap b/webapp/pages/admin/users/__snapshots__/_id.spec.js.snap new file mode 100644 index 000000000..2c5ddc686 --- /dev/null +++ b/webapp/pages/admin/users/__snapshots__/_id.spec.js.snap @@ -0,0 +1,104 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`.vue renders 1`] = ` + +
+
+
+
+
+

+ + User1 + - + admin.badges.title + +

+ +

+ admin.badges.description +

+
+ +
+
+

+ admin.badges.verificationBadges +

+ +
+ + +
+
+ +
+

+ admin.badges.trophyBadges +

+ +
+ + +
+
+ + +
+
+
+
+
+ +`; diff --git a/webapp/pages/admin/users/_id.spec.js b/webapp/pages/admin/users/_id.spec.js new file mode 100644 index 000000000..933de58de --- /dev/null +++ b/webapp/pages/admin/users/_id.spec.js @@ -0,0 +1,326 @@ +import { render, fireEvent, screen } from '@testing-library/vue' +import BadgesPage from './_id.vue' + +const localVue = global.localVue + +const availableBadges = [ + { + id: 'verification-badge-1', + icon: 'icon1', + type: 'verification', + description: 'description-v-1', + }, + { + id: 'verification-badge-2', + icon: 'icon2', + type: 'verification', + description: 'description-v-2', + }, + { + id: 'trophy-badge-1', + icon: 'icon3', + type: 'trophy', + description: 'description-t-1', + }, + { + id: 'trophy-badge-2', + icon: 'icon4', + type: 'trophy', + description: 'description-t-2', + }, +] + +const user = { + id: 'user1', + name: 'User1', + badgeVerification: { + id: 'verification-badge-1', + }, + badgeTrophies: [ + { + id: 'trophy-badge-2', + }, + ], +} + +describe('.vue', () => { + let wrapper + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn((v) => v), + $apollo: { + User: { + query: jest.fn(), + }, + badges: { + query: jest.fn(), + }, + mutate: jest.fn(), + }, + $toast: { + success: jest.fn(), + error: jest.fn(), + }, + } + }) + const Wrapper = () => { + return render(BadgesPage, { + mocks, + localVue, + data: () => ({ + user, + badges: availableBadges, + }), + }) + } + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.baseElement).toMatchSnapshot() + }) + + describe('after clicking an inactive verification badge', () => { + let button + beforeEach(() => { + button = screen.getByAltText(availableBadges[1].description) + }) + + describe('and successful server response', () => { + beforeEach(async () => { + mocks.$apollo.mutate.mockResolvedValue({ + data: { + setVerificationBadge: { + id: 'user1', + badgeVerification: { + id: availableBadges[1].id, + }, + badgeTrophies: [], + }, + }, + }) + await fireEvent.click(button) + }) + + it('calls the mutation', async () => { + expect(mocks.$apollo.mutate).toHaveBeenCalledWith({ + mutation: expect.anything(), + variables: { + badgeId: availableBadges[1].id, + userId: 'user1', + }, + }) + }) + + it('shows success message', async () => { + expect(mocks.$toast.success).toHaveBeenCalledWith('admin.badges.setVerification.success') + }) + }) + + describe('and failed server response', () => { + beforeEach(async () => { + mocks.$apollo.mutate.mockRejectedValue({ message: 'Ouch!' }) + await fireEvent.click(button) + }) + + it('calls the mutation', async () => { + expect(mocks.$apollo.mutate).toHaveBeenCalledWith({ + mutation: expect.anything(), + variables: { + badgeId: availableBadges[1].id, + userId: 'user1', + }, + }) + }) + + it('shows error message', async () => { + expect(mocks.$toast.error).toHaveBeenCalledWith('admin.badges.setVerification.error') + }) + }) + + describe('after clicking an inactive trophy badge', () => { + let button + beforeEach(() => { + button = screen.getByAltText(availableBadges[2].description) + }) + + describe('and successful server response', () => { + beforeEach(async () => { + mocks.$apollo.mutate.mockResolvedValue({ + data: { + setTrophyBadge: { + id: 'user1', + badgeVerification: null, + badgeTrophies: [ + { + id: availableBadges[2].id, + }, + ], + }, + }, + }) + await fireEvent.click(button) + }) + + it('calls the mutation', async () => { + expect(mocks.$apollo.mutate).toHaveBeenCalledWith({ + mutation: expect.anything(), + variables: { + badgeId: availableBadges[2].id, + userId: 'user1', + }, + }) + }) + + it('shows success message', async () => { + expect(mocks.$toast.success).toHaveBeenCalledWith('admin.badges.rewardTrophy.success') + }) + }) + + describe('and failed server response', () => { + beforeEach(async () => { + mocks.$apollo.mutate.mockRejectedValue({ message: 'Ouch!' }) + await fireEvent.click(button) + }) + + it('calls the mutation', async () => { + expect(mocks.$apollo.mutate).toHaveBeenCalledWith({ + mutation: expect.anything(), + variables: { + badgeId: availableBadges[2].id, + userId: 'user1', + }, + }) + }) + + it('shows error message', async () => { + expect(mocks.$toast.error).toHaveBeenCalledWith('admin.badges.rewardTrophy.error') + }) + }) + }) + + describe('after clicking an active verification badge', () => { + let button + beforeEach(() => { + button = screen.getByAltText(availableBadges[0].description) + }) + + describe('and successful server response', () => { + beforeEach(async () => { + mocks.$apollo.mutate.mockResolvedValue({ + data: { + setVerificationBadge: { + id: 'user1', + badgeVerification: null, + badgeTrophies: [], + }, + }, + }) + await fireEvent.click(button) + }) + + it('calls the mutation', async () => { + expect(mocks.$apollo.mutate).toHaveBeenCalledWith({ + mutation: expect.anything(), + variables: { + badgeId: availableBadges[0].id, + userId: 'user1', + }, + }) + }) + + it('shows success message', async () => { + expect(mocks.$toast.success).toHaveBeenCalledWith( + 'admin.badges.revokeVerification.success', + ) + }) + }) + + describe('and failed server response', () => { + beforeEach(async () => { + mocks.$apollo.mutate.mockRejectedValue({ message: 'Ouch!' }) + await fireEvent.click(button) + }) + + it('calls the mutation', async () => { + expect(mocks.$apollo.mutate).toHaveBeenCalledWith({ + mutation: expect.anything(), + variables: { + badgeId: availableBadges[0].id, + userId: 'user1', + }, + }) + }) + + it('shows error message', async () => { + expect(mocks.$toast.error).toHaveBeenCalledWith('admin.badges.revokeVerification.error') + }) + }) + }) + }) + + describe('after clicking an active trophy badge', () => { + let button + beforeEach(() => { + button = screen.getByAltText(availableBadges[3].description) + }) + + describe('and successful server response', () => { + beforeEach(async () => { + mocks.$apollo.mutate.mockResolvedValue({ + data: { + setTrophyBadge: { + id: 'user1', + badgeVerification: null, + badgeTrophies: [ + { + id: availableBadges[3].id, + }, + ], + }, + }, + }) + await fireEvent.click(button) + }) + + it('calls the mutation', async () => { + expect(mocks.$apollo.mutate).toHaveBeenCalledWith({ + mutation: expect.anything(), + variables: { + badgeId: availableBadges[3].id, + userId: 'user1', + }, + }) + }) + + it('shows success message', async () => { + expect(mocks.$toast.success).toHaveBeenCalledWith('admin.badges.revokeTrophy.success') + }) + }) + + describe('and failed server response', () => { + beforeEach(async () => { + mocks.$apollo.mutate.mockRejectedValue({ message: 'Ouch!' }) + await fireEvent.click(button) + }) + + it('calls the mutation', async () => { + expect(mocks.$apollo.mutate).toHaveBeenCalledWith({ + mutation: expect.anything(), + variables: { + badgeId: availableBadges[3].id, + userId: 'user1', + }, + }) + }) + + it('shows error message', async () => { + expect(mocks.$toast.error).toHaveBeenCalledWith('admin.badges.revokeTrophy.error') + }) + }) + }) +}) diff --git a/webapp/pages/admin/users/_id.vue b/webapp/pages/admin/users/_id.vue new file mode 100644 index 000000000..808e1653a --- /dev/null +++ b/webapp/pages/admin/users/_id.vue @@ -0,0 +1,163 @@ + + + diff --git a/webapp/pages/admin/users.spec.js b/webapp/pages/admin/users/index.spec.js similarity index 99% rename from webapp/pages/admin/users.spec.js rename to webapp/pages/admin/users/index.spec.js index 43c51fb52..8d6b923c5 100644 --- a/webapp/pages/admin/users.spec.js +++ b/webapp/pages/admin/users/index.spec.js @@ -1,6 +1,6 @@ import { mount } from '@vue/test-utils' import Vuex from 'vuex' -import Users from './users.vue' +import Users from './index.vue' const localVue = global.localVue diff --git a/webapp/pages/admin/users.vue b/webapp/pages/admin/users/index.vue similarity index 93% rename from webapp/pages/admin/users.vue rename to webapp/pages/admin/users/index.vue index 44f162c77..24258a57f 100644 --- a/webapp/pages/admin/users.vue +++ b/webapp/pages/admin/users/index.vue @@ -63,6 +63,16 @@ {{ scope.row.role }} + @@ -132,6 +142,10 @@ export default { label: this.$t('admin.users.table.columns.role'), align: 'right', }, + badges: { + label: this.$t('admin.users.table.columns.badges'), + align: 'right', + }, } }, }, From e58efb1ce0b8c88e06dccded409f91112dd7494a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 23 Apr 2025 13:56:17 +0200 Subject: [PATCH 28/54] refactor(webapp): refactor branding diverse v2 (#8427) * Set new 'config.resolve.alias' * Set new alias 'compilerOptions.paths' --- webapp/jsconfig.json | 13 +++++++++++-- webapp/nuxt.config.js | 3 +++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/webapp/jsconfig.json b/webapp/jsconfig.json index 7e3695e4e..98874805a 100644 --- a/webapp/jsconfig.json +++ b/webapp/jsconfig.json @@ -3,11 +3,20 @@ "baseUrl": ".", "paths": { "~/*": [ - "./*" + "*" ], "~*": [ + "*" + ], + "~@": [ + "*" + ], + "@": [ + "*" + ], + "@@/*": [ "./*" ], } } -} \ No newline at end of file +} diff --git a/webapp/nuxt.config.js b/webapp/nuxt.config.js index 1c963615a..07cfa6bc4 100644 --- a/webapp/nuxt.config.js +++ b/webapp/nuxt.config.js @@ -270,6 +270,9 @@ export default { }, } + config.resolve.alias['~@'] = path.resolve(__dirname, '/') + config.resolve.alias['@@'] = path.resolve(__dirname, '/') + if (CONFIG.STYLEGUIDE_DEV) { config.resolve.alias['@@'] = path.resolve(__dirname, `${styleguidePath}/src/system`) config.module.rules.push({ From 6b40a0dc590e3063306f4e821bc4907c110aa355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 23 Apr 2025 23:24:41 +0800 Subject: [PATCH 29/54] chore(frontend): run npm install (#8432) Just running `npm install` leads to local changes. Why are they not checked in? I'm using the specified node version from `/.tool-versions` in the root directory. --- frontend/package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 63b54a127..c912f54a9 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "ocelot-social-frontend", - "version": "3.2.1", + "version": "3.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ocelot-social-frontend", - "version": "3.2.1", + "version": "3.3.0", "license": "Apache-2.0", "dependencies": { "@intlify/unplugin-vue-i18n": "^2.0.0", From 873cd6cd3409345708c7cc2455cb07e81fdf94bb Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Wed, 23 Apr 2025 18:21:06 +0200 Subject: [PATCH 30/54] refactor(backend): allow to set selected badge-slot to null (#8421) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * allow to set selected badgeslot to null Free a specific badge slot by setting it to null * Update backend/src/schema/resolvers/users.ts Co-authored-by: Wolfgang Huß --------- Co-authored-by: Wolfgang Huß Co-authored-by: Max --- backend/src/graphql/types/type/User.gql | 2 +- backend/src/schema/resolvers/users.spec.ts | 49 +++++++++++++++++++++- backend/src/schema/resolvers/users.ts | 18 +++++--- 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/backend/src/graphql/types/type/User.gql b/backend/src/graphql/types/type/User.gql index f1a2bcc15..751931d5b 100644 --- a/backend/src/graphql/types/type/User.gql +++ b/backend/src/graphql/types/type/User.gql @@ -252,6 +252,6 @@ type Mutation { # Get a JWT Token for the given Email and password login(email: String!, password: String!): String! - setTrophyBadgeSelected(slot: Int!, badgeId: ID!): User + setTrophyBadgeSelected(slot: Int!, badgeId: ID): User resetTrophyBadgesSelected: User } diff --git a/backend/src/schema/resolvers/users.spec.ts b/backend/src/schema/resolvers/users.spec.ts index 0a74d46d3..8d082b682 100644 --- a/backend/src/schema/resolvers/users.spec.ts +++ b/backend/src/schema/resolvers/users.spec.ts @@ -76,7 +76,7 @@ const updateOnlineStatus = gql` ` const setTrophyBadgeSelected = gql` - mutation ($slot: Int!, $badgeId: ID!) { + mutation ($slot: Int!, $badgeId: ID) { setTrophyBadgeSelected(slot: $slot, badgeId: $badgeId) { badgeTrophiesCount badgeTrophiesSelected { @@ -1294,6 +1294,53 @@ describe('setTrophyBadgeSelected', () => { }), ) }) + + describe('set badge to null', () => { + it('returns the user with no badge set on the selected slot', async () => { + await mutate({ + mutation: setTrophyBadgeSelected, + variables: { slot: 0, badgeId: 'trophy_bear' }, + }) + await mutate({ + mutation: setTrophyBadgeSelected, + variables: { slot: 5, badgeId: 'trophy_panda' }, + }) + + await expect( + mutate({ + mutation: setTrophyBadgeSelected, + variables: { slot: 5, badgeId: null }, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + setTrophyBadgeSelected: { + badgeTrophiesCount: 2, + badgeTrophiesSelected: [ + { + id: 'trophy_bear', + }, + null, + null, + null, + null, + null, + null, + null, + null, + ], + badgeTrophiesUnused: [ + { + id: 'trophy_panda', + }, + ], + badgeTrophiesUnusedCount: 1, + }, + }, + }), + ) + }) + }) }) }) diff --git a/backend/src/schema/resolvers/users.ts b/backend/src/schema/resolvers/users.ts index 4f1fb6d5b..913427085 100644 --- a/backend/src/schema/resolvers/users.ts +++ b/backend/src/schema/resolvers/users.ts @@ -404,17 +404,25 @@ export default { const session = context.driver.session() const query = session.writeTransaction(async (transaction) => { - const result = await transaction.run( - ` + const queryBadge = ` MATCH (user:User {id: $userId})<-[:REWARDED]-(badge:Badge {id: $badgeId}) OPTIONAL MATCH (user)-[badgeRelation:SELECTED]->(badge) OPTIONAL MATCH (user)-[slotRelation:SELECTED{slot: $slot}]->(:Badge) DELETE badgeRelation, slotRelation MERGE (user)-[:SELECTED{slot: toInteger($slot)}]->(badge) RETURN user {.*} - `, - { userId, badgeId, slot }, - ) + ` + const queryNull = ` + MATCH (user:User {id: $userId}) + OPTIONAL MATCH (user)-[slotRelation:SELECTED {slot: $slot}]->(:Badge) + DELETE slotRelation + RETURN user {.*} + ` + const result = await transaction.run(badgeId ? queryBadge : queryNull, { + userId, + badgeId, + slot, + }) return result.records.map((record) => record.get('user'))[0] }) try { From 5883818b9116926b11c70fc2ee2bbac95fc0f25c Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Wed, 23 Apr 2025 19:12:24 +0200 Subject: [PATCH 31/54] refactor(backend): default badges, always return a badge (#8430) * default badges, always return a badge - default badges for trophy and verification - always return a badge instead of null - isDefault field on Badge lint fixes * default_verification svg * add default-trophy Co-authored-by: Sebastian Stein --------- Co-authored-by: Sebastian Stein --- backend/public/img/badges/default_trophy.svg | 12 + .../img/badges/default_verification.svg | 28 +++ backend/src/graphql/types/type/Badge.gql | 1 + backend/src/graphql/types/type/User.gql | 4 +- backend/src/schema/resolvers/badges.spec.ts | 19 +- backend/src/schema/resolvers/badges.ts | 20 ++ backend/src/schema/resolvers/users.spec.ts | 227 ++++++++++++++++-- backend/src/schema/resolvers/users.ts | 56 +++-- 8 files changed, 311 insertions(+), 56 deletions(-) create mode 100644 backend/public/img/badges/default_trophy.svg create mode 100644 backend/public/img/badges/default_verification.svg diff --git a/backend/public/img/badges/default_trophy.svg b/backend/public/img/badges/default_trophy.svg new file mode 100644 index 000000000..b203cdfc6 --- /dev/null +++ b/backend/public/img/badges/default_trophy.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/backend/public/img/badges/default_verification.svg b/backend/public/img/badges/default_verification.svg new file mode 100644 index 000000000..7bde29f35 --- /dev/null +++ b/backend/public/img/badges/default_verification.svg @@ -0,0 +1,28 @@ + + + + + + + + + + diff --git a/backend/src/graphql/types/type/Badge.gql b/backend/src/graphql/types/type/Badge.gql index cbfe0193d..8cdad2ee7 100644 --- a/backend/src/graphql/types/type/Badge.gql +++ b/backend/src/graphql/types/type/Badge.gql @@ -4,6 +4,7 @@ type Badge { icon: String! createdAt: String description: String! + isDefault: Boolean! rewarded: [User]! @relation(name: "REWARDED", direction: "OUT") verifies: [User]! @relation(name: "VERIFIES", direction: "OUT") diff --git a/backend/src/graphql/types/type/User.gql b/backend/src/graphql/types/type/User.gql index 751931d5b..81dd9cf5b 100644 --- a/backend/src/graphql/types/type/User.gql +++ b/backend/src/graphql/types/type/User.gql @@ -125,10 +125,10 @@ type User { categories: [Category] @relation(name: "CATEGORIZED", direction: "OUT") - badgeVerification: Badge @relation(name: "VERIFIES", direction: "IN") + badgeVerification: Badge! @neo4j_ignore badgeTrophies: [Badge]! @relation(name: "REWARDED", direction: "IN") badgeTrophiesCount: Int! @cypher(statement: "MATCH (this)<-[:REWARDED]-(r:Badge) RETURN COUNT(r)") - badgeTrophiesSelected: [Badge]! @neo4j_ignore + badgeTrophiesSelected: [Badge!]! @neo4j_ignore badgeTrophiesUnused: [Badge]! @neo4j_ignore badgeTrophiesUnusedCount: Int! @neo4j_ignore diff --git a/backend/src/schema/resolvers/badges.spec.ts b/backend/src/schema/resolvers/badges.spec.ts index 17fe46c99..2588c9b5c 100644 --- a/backend/src/schema/resolvers/badges.spec.ts +++ b/backend/src/schema/resolvers/badges.spec.ts @@ -100,6 +100,7 @@ describe('Badges', () => { id badgeVerification { id + isDefault } badgeTrophies { id @@ -204,7 +205,7 @@ describe('Badges', () => { data: { setVerificationBadge: { id: 'regular-user-id', - badgeVerification: { id: 'verification_moderator' }, + badgeVerification: { id: 'verification_moderator', isDefault: false }, badgeTrophies: [], }, }, @@ -226,7 +227,7 @@ describe('Badges', () => { data: { setVerificationBadge: { id: 'regular-user-id', - badgeVerification: { id: 'verification_admin' }, + badgeVerification: { id: 'verification_admin', isDefault: false }, badgeTrophies: [], }, }, @@ -255,7 +256,7 @@ describe('Badges', () => { data: { setVerificationBadge: { id: 'regular-user-2-id', - badgeVerification: { id: 'verification_moderator' }, + badgeVerification: { id: 'verification_moderator', isDefault: false }, badgeTrophies: [], }, }, @@ -299,6 +300,7 @@ describe('Badges', () => { id badgeVerification { id + isDefault } badgeTrophies { id @@ -403,7 +405,7 @@ describe('Badges', () => { data: { rewardTrophyBadge: { id: 'regular-user-id', - badgeVerification: null, + badgeVerification: { id: 'default_verification', isDefault: true }, badgeTrophies: [{ id: 'trophy_rhino' }], }, }, @@ -530,6 +532,7 @@ describe('Badges', () => { id badgeVerification { id + isDefault } badgeTrophies { id @@ -596,7 +599,7 @@ describe('Badges', () => { data: { revokeBadge: { id: 'regular-user-id', - badgeVerification: { id: 'verification_moderator' }, + badgeVerification: { id: 'verification_moderator', isDefault: false }, badgeTrophies: [], }, }, @@ -610,7 +613,7 @@ describe('Badges', () => { data: { revokeBadge: { id: 'regular-user-id', - badgeVerification: { id: 'verification_moderator' }, + badgeVerification: { id: 'verification_moderator', isDefault: false }, badgeTrophies: [], }, }, @@ -631,7 +634,7 @@ describe('Badges', () => { data: { revokeBadge: { id: 'regular-user-id', - badgeVerification: null, + badgeVerification: { id: 'default_verification', isDefault: true }, badgeTrophies: [{ id: 'trophy_rhino' }], }, }, @@ -659,7 +662,7 @@ describe('Badges', () => { data: { revokeBadge: { id: 'regular-user-id', - badgeVerification: null, + badgeVerification: { id: 'default_verification', isDefault: true }, badgeTrophies: [{ id: 'trophy_rhino' }], }, }, diff --git a/backend/src/schema/resolvers/badges.ts b/backend/src/schema/resolvers/badges.ts index 430e3bf75..587204b54 100644 --- a/backend/src/schema/resolvers/badges.ts +++ b/backend/src/schema/resolvers/badges.ts @@ -6,6 +6,22 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { neo4jgraphql } from 'neo4j-graphql-js' +export const defaultTrophyBadge = { + id: 'default_trophy', + type: 'trophy', + icon: '/img/badges/default_trophy.svg', + description: '', + createdAt: '', +} + +export const defaultVerificationBadge = { + id: 'default_verification', + type: 'verification', + icon: '/img/badges/default_verification.svg', + description: '', + createdAt: '', +} + export default { Query: { Badge: async (object, args, context, resolveInfo) => @@ -123,4 +139,8 @@ export default { } }, }, + Badge: { + isDefault: async (parent, _params, _context, _resolveInfo) => + [defaultTrophyBadge.id, defaultVerificationBadge.id].includes(parent.id), + }, } diff --git a/backend/src/schema/resolvers/users.spec.ts b/backend/src/schema/resolvers/users.spec.ts index 8d082b682..d4f5e00eb 100644 --- a/backend/src/schema/resolvers/users.spec.ts +++ b/backend/src/schema/resolvers/users.spec.ts @@ -81,6 +81,7 @@ const setTrophyBadgeSelected = gql` badgeTrophiesCount badgeTrophiesSelected { id + isDefault } badgeTrophiesUnused { id @@ -96,6 +97,7 @@ const resetTrophyBadgesSelected = gql` badgeTrophiesCount badgeTrophiesSelected { id + isDefault } badgeTrophiesUnused { id @@ -1242,15 +1244,40 @@ describe('setTrophyBadgeSelected', () => { badgeTrophiesSelected: [ { id: 'trophy_bear', + isDefault: false, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, }, - null, - null, - null, - null, - null, - null, - null, - null, ], badgeTrophiesUnused: [ { @@ -1275,17 +1302,40 @@ describe('setTrophyBadgeSelected', () => { badgeTrophiesSelected: [ { id: 'trophy_bear', + isDefault: false, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, }, - null, - null, - null, - null, { id: 'trophy_panda', + isDefault: false, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, }, - null, - null, - null, ], badgeTrophiesUnused: [], badgeTrophiesUnusedCount: 0, @@ -1295,8 +1345,8 @@ describe('setTrophyBadgeSelected', () => { ) }) - describe('set badge to null', () => { - it('returns the user with no badge set on the selected slot', async () => { + describe('set badge to null or default', () => { + beforeEach(async () => { await mutate({ mutation: setTrophyBadgeSelected, variables: { slot: 0, badgeId: 'trophy_bear' }, @@ -1305,7 +1355,9 @@ describe('setTrophyBadgeSelected', () => { mutation: setTrophyBadgeSelected, variables: { slot: 5, badgeId: 'trophy_panda' }, }) + }) + it('returns the user with no badge set on the selected slot when sending null', async () => { await expect( mutate({ mutation: setTrophyBadgeSelected, @@ -1319,15 +1371,101 @@ describe('setTrophyBadgeSelected', () => { badgeTrophiesSelected: [ { id: 'trophy_bear', + isDefault: false, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + ], + badgeTrophiesUnused: [ + { + id: 'trophy_panda', + }, + ], + badgeTrophiesUnusedCount: 1, + }, + }, + }), + ) + }) + + it('returns the user with no badge set on the selected slot when sending default_trophy', async () => { + await expect( + mutate({ + mutation: setTrophyBadgeSelected, + variables: { slot: 5, badgeId: 'default_trophy' }, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + setTrophyBadgeSelected: { + badgeTrophiesCount: 2, + badgeTrophiesSelected: [ + { + id: 'trophy_bear', + isDefault: false, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, }, - null, - null, - null, - null, - null, - null, - null, - null, ], badgeTrophiesUnused: [ { @@ -1411,7 +1549,44 @@ describe('resetTrophyBadgesSelected', () => { data: { resetTrophyBadgesSelected: { badgeTrophiesCount: 2, - badgeTrophiesSelected: [null, null, null, null, null, null, null, null, null], + badgeTrophiesSelected: [ + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + ], badgeTrophiesUnused: [ { id: 'trophy_panda', diff --git a/backend/src/schema/resolvers/users.ts b/backend/src/schema/resolvers/users.ts index 913427085..c165e8e44 100644 --- a/backend/src/schema/resolvers/users.ts +++ b/backend/src/schema/resolvers/users.ts @@ -11,6 +11,7 @@ import { neo4jgraphql } from 'neo4j-graphql-js' import { TROPHY_BADGES_SELECTED_MAX } from '@constants/badges' import { getNeode } from '@db/neo4j' +import { defaultTrophyBadge, defaultVerificationBadge } from './badges' import log from './helpers/databaseLogger' import Resolver from './helpers/Resolver' import { mergeImage, deleteImage } from './images/images' @@ -412,13 +413,15 @@ export default { MERGE (user)-[:SELECTED{slot: toInteger($slot)}]->(badge) RETURN user {.*} ` - const queryNull = ` + const queryEmpty = ` MATCH (user:User {id: $userId}) OPTIONAL MATCH (user)-[slotRelation:SELECTED {slot: $slot}]->(:Badge) DELETE slotRelation RETURN user {.*} ` - const result = await transaction.run(badgeId ? queryBadge : queryNull, { + const isDefault = !badgeId || badgeId === defaultTrophyBadge.id + + const result = await transaction.run(isDefault ? queryEmpty : queryBadge, { userId, badgeId, slot, @@ -538,7 +541,7 @@ export default { }) try { const badgesSelected = await query - const result = Array(TROPHY_BADGES_SELECTED_MAX).fill(null) + const result = Array(TROPHY_BADGES_SELECTED_MAX).fill(defaultTrophyBadge) badgesSelected.map((record) => { result[record.get('slot')] = record.get('badge') return true @@ -550,21 +553,17 @@ export default { session.close() } }, - badgeTrophiesUnused: async (_parent, _params, context, _resolveInfo) => { - const { - user: { id: userId }, - } = context - + badgeTrophiesUnused: async (parent, _params, context, _resolveInfo) => { const session = context.driver.session() - const query = session.writeTransaction(async (transaction) => { + const query = session.readTransaction(async (transaction) => { const result = await transaction.run( ` - MATCH (user:User {id: $userId})<-[:REWARDED]-(badge:Badge) + MATCH (user:User {id: $parent.id})<-[:REWARDED]-(badge:Badge) WHERE NOT (user)-[:SELECTED]-(badge) RETURN badge {.*} `, - { userId }, + { parent }, ) return result.records.map((record) => record.get('badge')) }) @@ -576,21 +575,17 @@ export default { session.close() } }, - badgeTrophiesUnusedCount: async (_parent, _params, context, _resolveInfo) => { - const { - user: { id: userId }, - } = context - + badgeTrophiesUnusedCount: async (parent, _params, context, _resolveInfo) => { const session = context.driver.session() - const query = session.writeTransaction(async (transaction) => { + const query = session.readTransaction(async (transaction) => { const result = await transaction.run( ` - MATCH (user:User {id: $userId})<-[:REWARDED]-(badge:Badge) + MATCH (user:User {id: $parent.id})<-[:REWARDED]-(badge:Badge) WHERE NOT (user)-[:SELECTED]-(badge) RETURN toString(COUNT(badge)) as count `, - { userId }, + { parent }, ) return result.records.map((record) => record.get('count'))[0] }) @@ -602,6 +597,28 @@ export default { session.close() } }, + badgeVerification: async (parent, _params, context, _resolveInfo) => { + const session = context.driver.session() + + const query = session.writeTransaction(async (transaction) => { + const result = await transaction.run( + ` + MATCH (user:User {id: $parent.id})<-[:VERIFIES]-(verification:Badge) + RETURN verification {.*} + `, + { parent }, + ) + return result.records.map((record) => record.get('verification'))[0] + }) + try { + const result = await query + return result ?? defaultVerificationBadge + } catch (error) { + throw new Error(error) + } finally { + session.close() + } + }, ...Resolver('User', { undefinedToNull: [ 'actorId', @@ -642,7 +659,6 @@ export default { invitedBy: '<-[:INVITED]-(related:User)', location: '-[:IS_IN]->(related:Location)', redeemedInviteCode: '-[:REDEEMED]->(related:InviteCode)', - badgeVerification: '<-[:VERIFIES]-(related:Badge)', }, hasMany: { followedBy: '<-[:FOLLOWS]-(related:User)', From 649491f7cbe312fb4101bc32e6a4d81af94c38e0 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 24 Apr 2025 00:58:53 +0200 Subject: [PATCH 32/54] fix(backend): fix notification emails with different name (#8419) * fix diffent name notifications We had emails sent with incorrect names. This PR combines the query for the email with the user the notification is sent to since the notification in database was correct. The underlying problem is the unstable order in which the database can return values. The results of the two queries were matched by id since it was assumed that they always return the same order of elements. lint fixes fix typo fix factory fix tests * fix tests accoridng to review also test for the right amount of emails in every test --- .../notificationsMiddleware.emails.spec.ts | 86 +++++++---- ...ficationsMiddleware.followed-users.spec.ts | 80 ++++++++-- ...tionsMiddleware.mentions-in-groups.spec.ts | 71 +++++++-- ...icationsMiddleware.observing-posts.spec.ts | 74 +++++++-- ...ificationsMiddleware.online-status.spec.ts | 6 +- ...icationsMiddleware.posts-in-groups.spec.ts | 30 +++- .../notificationsMiddleware.spec.ts | 42 ++--- .../notifications/notificationsMiddleware.ts | 146 +++++++----------- 8 files changed, 355 insertions(+), 180 deletions(-) diff --git a/backend/src/middleware/notifications/notificationsMiddleware.emails.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.emails.spec.ts index 78c95b454..55edef940 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.emails.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.emails.spec.ts @@ -16,20 +16,21 @@ import createServer from '@src/server' CONFIG.CATEGORIES_ACTIVE = false -const sendMailMock = jest.fn() -jest.mock('../helpers/email/sendMail', () => ({ - sendMail: () => sendMailMock(), +const sendMailMock: (notification) => void = jest.fn() +jest.mock('@middleware/helpers/email/sendMail', () => ({ + sendMail: (notification) => sendMailMock(notification), })) -let server, query, mutate, authenticatedUser +let server, query, mutate, authenticatedUser, emaillessMember let postAuthor, groupMember const driver = getDriver() const neode = getNeode() -const mentionString = - '@group-member' +const mentionString = ` + @group-member + @email-less-member` const createPostMutation = gql` mutation ($id: ID, $title: String!, $content: String!, $groupId: ID) { @@ -148,6 +149,11 @@ describe('emails sent for notifications', () => { password: '1234', }, ) + emaillessMember = await neode.create('User', { + id: 'email-less-member', + name: 'Email-less Member', + slug: 'email-less-member', + }) authenticatedUser = await postAuthor.toJson() await mutate({ mutation: createGroupMutation(), @@ -171,6 +177,18 @@ describe('emails sent for notifications', () => { mutation: followUserMutation, variables: { id: 'post-author' }, }) + authenticatedUser = await emaillessMember.toJson() + await mutate({ + mutation: joinGroupMutation(), + variables: { + groupId: 'public-group', + userId: 'group-member', + }, + }) + await mutate({ + mutation: followUserMutation, + variables: { id: 'post-author' }, + }) }) afterEach(async () => { @@ -188,7 +206,7 @@ describe('emails sent for notifications', () => { variables: { id: 'post', title: 'This is the post', - content: `Hello, ${mentionString}, my trusty follower.`, + content: `Hello, ${mentionString}, my trusty followers.`, groupId: 'public-group', }, }) @@ -213,7 +231,7 @@ describe('emails sent for notifications', () => { __typename: 'Post', id: 'post', content: - 'Hello, @group-member, my trusty follower.', + 'Hello,
@group-member
@email-less-member, my trusty followers.', }, read: false, reason: 'post_in_group', @@ -225,7 +243,7 @@ describe('emails sent for notifications', () => { __typename: 'Post', id: 'post', content: - 'Hello, @group-member, my trusty follower.', + 'Hello,
@group-member
@email-less-member, my trusty followers.', }, read: false, reason: 'followed_user_posted', @@ -237,7 +255,7 @@ describe('emails sent for notifications', () => { __typename: 'Post', id: 'post', content: - 'Hello, @group-member, my trusty follower.', + 'Hello,
@group-member
@email-less-member, my trusty followers.', }, read: false, reason: 'mentioned_in_post', @@ -260,7 +278,7 @@ describe('emails sent for notifications', () => { variables: { id: 'post', title: 'This is the post', - content: `Hello, ${mentionString}, my trusty follower.`, + content: `Hello, ${mentionString}, my trusty followers.`, groupId: 'public-group', }, }) @@ -285,7 +303,7 @@ describe('emails sent for notifications', () => { __typename: 'Post', id: 'post', content: - 'Hello, @group-member, my trusty follower.', + 'Hello,
@group-member
@email-less-member, my trusty followers.', }, read: false, reason: 'post_in_group', @@ -297,7 +315,7 @@ describe('emails sent for notifications', () => { __typename: 'Post', id: 'post', content: - 'Hello, @group-member, my trusty follower.', + 'Hello,
@group-member
@email-less-member, my trusty followers.', }, read: false, reason: 'followed_user_posted', @@ -309,7 +327,7 @@ describe('emails sent for notifications', () => { __typename: 'Post', id: 'post', content: - 'Hello, @group-member, my trusty follower.', + 'Hello,
@group-member
@email-less-member, my trusty followers.', }, read: false, reason: 'mentioned_in_post', @@ -333,7 +351,7 @@ describe('emails sent for notifications', () => { variables: { id: 'post', title: 'This is the post', - content: `Hello, ${mentionString}, my trusty follower.`, + content: `Hello, ${mentionString}, my trusty followers.`, groupId: 'public-group', }, }) @@ -358,7 +376,7 @@ describe('emails sent for notifications', () => { __typename: 'Post', id: 'post', content: - 'Hello, @group-member, my trusty follower.', + 'Hello,
@group-member
@email-less-member, my trusty followers.', }, read: false, reason: 'post_in_group', @@ -370,7 +388,7 @@ describe('emails sent for notifications', () => { __typename: 'Post', id: 'post', content: - 'Hello, @group-member, my trusty follower.', + 'Hello,
@group-member
@email-less-member, my trusty followers.', }, read: false, reason: 'followed_user_posted', @@ -382,7 +400,7 @@ describe('emails sent for notifications', () => { __typename: 'Post', id: 'post', content: - 'Hello, @group-member, my trusty follower.', + 'Hello,
@group-member
@email-less-member, my trusty followers.', }, read: false, reason: 'mentioned_in_post', @@ -407,7 +425,7 @@ describe('emails sent for notifications', () => { variables: { id: 'post', title: 'This is the post', - content: `Hello, ${mentionString}, my trusty follower.`, + content: `Hello, ${mentionString}, my trusty followers.`, groupId: 'public-group', }, }) @@ -432,7 +450,7 @@ describe('emails sent for notifications', () => { __typename: 'Post', id: 'post', content: - 'Hello, @group-member, my trusty follower.', + 'Hello,
@group-member
@email-less-member, my trusty followers.', }, read: false, reason: 'post_in_group', @@ -444,7 +462,7 @@ describe('emails sent for notifications', () => { __typename: 'Post', id: 'post', content: - 'Hello, @group-member, my trusty follower.', + 'Hello,
@group-member
@email-less-member, my trusty followers.', }, read: false, reason: 'followed_user_posted', @@ -456,7 +474,7 @@ describe('emails sent for notifications', () => { __typename: 'Post', id: 'post', content: - 'Hello, @group-member, my trusty follower.', + 'Hello,
@group-member
@email-less-member, my trusty followers.', }, read: false, reason: 'mentioned_in_post', @@ -481,7 +499,7 @@ describe('emails sent for notifications', () => { variables: { id: 'post', title: 'This is the post', - content: `Hello, ${mentionString}, my trusty follower.`, + content: `Hello, ${mentionString}, my trusty followers.`, groupId: 'public-group', }, }) @@ -501,7 +519,7 @@ describe('emails sent for notifications', () => { mutation: createCommentMutation, variables: { id: 'comment-2', - content: `Hello, ${mentionString}, my beloved follower.`, + content: `Hello, ${mentionString}, my trusty followers.`, postId: 'post', }, }) @@ -529,7 +547,7 @@ describe('emails sent for notifications', () => { __typename: 'Comment', id: 'comment-2', content: - 'Hello, @group-member, my beloved follower.', + 'Hello,
@group-member
@email-less-member, my trusty followers.', }, read: false, reason: 'commented_on_post', @@ -541,7 +559,7 @@ describe('emails sent for notifications', () => { __typename: 'Comment', id: 'comment-2', content: - 'Hello, @group-member, my beloved follower.', + 'Hello,
@group-member
@email-less-member, my trusty followers.', }, read: false, reason: 'mentioned_in_comment', @@ -563,7 +581,7 @@ describe('emails sent for notifications', () => { variables: { id: 'post', title: 'This is the post', - content: `Hello, ${mentionString}, my trusty follower.`, + content: `Hello, ${mentionString}, my trusty followers.`, groupId: 'public-group', }, }) @@ -583,7 +601,7 @@ describe('emails sent for notifications', () => { mutation: createCommentMutation, variables: { id: 'comment-2', - content: `Hello, ${mentionString}, my beloved follower.`, + content: `Hello, ${mentionString}, my trusty followers.`, postId: 'post', }, }) @@ -611,7 +629,7 @@ describe('emails sent for notifications', () => { __typename: 'Comment', id: 'comment-2', content: - 'Hello, @group-member, my beloved follower.', + 'Hello,
@group-member
@email-less-member, my trusty followers.', }, read: false, reason: 'commented_on_post', @@ -623,7 +641,7 @@ describe('emails sent for notifications', () => { __typename: 'Comment', id: 'comment-2', content: - 'Hello, @group-member, my beloved follower.', + 'Hello,
@group-member
@email-less-member, my trusty followers.', }, read: false, reason: 'mentioned_in_comment', @@ -646,7 +664,7 @@ describe('emails sent for notifications', () => { variables: { id: 'post', title: 'This is the post', - content: `Hello, ${mentionString}, my trusty follower.`, + content: `Hello, ${mentionString}, my trusty followers.`, groupId: 'public-group', }, }) @@ -666,7 +684,7 @@ describe('emails sent for notifications', () => { mutation: createCommentMutation, variables: { id: 'comment-2', - content: `Hello, ${mentionString}, my beloved follower.`, + content: `Hello, ${mentionString}, my trusty followers.`, postId: 'post', }, }) @@ -694,7 +712,7 @@ describe('emails sent for notifications', () => { __typename: 'Comment', id: 'comment-2', content: - 'Hello, @group-member, my beloved follower.', + 'Hello,
@group-member
@email-less-member, my trusty followers.', }, read: false, reason: 'commented_on_post', @@ -706,7 +724,7 @@ describe('emails sent for notifications', () => { __typename: 'Comment', id: 'comment-2', content: - 'Hello, @group-member, my beloved follower.', + 'Hello,
@group-member
@email-less-member, my trusty followers.', }, read: false, reason: 'mentioned_in_comment', diff --git a/backend/src/middleware/notifications/notificationsMiddleware.followed-users.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.followed-users.spec.ts index 5be4ea5b5..18da6bff8 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.followed-users.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.followed-users.spec.ts @@ -14,14 +14,14 @@ import createServer from '@src/server' CONFIG.CATEGORIES_ACTIVE = false -const sendMailMock = jest.fn() -jest.mock('../helpers/email/sendMail', () => ({ - sendMail: () => sendMailMock(), +const sendMailMock: (notification) => void = jest.fn() +jest.mock('@middleware/helpers/email/sendMail', () => ({ + sendMail: (notification) => sendMailMock(notification), })) let server, query, mutate, authenticatedUser -let postAuthor, firstFollower, secondFollower +let postAuthor, firstFollower, secondFollower, thirdFollower, emaillessFollower const driver = getDriver() const neode = getNeode() @@ -107,7 +107,7 @@ describe('following users notifications', () => { slug: 'post-author', }, { - email: 'test@example.org', + email: 'post-author@example.org', password: '1234', }, ) @@ -119,7 +119,7 @@ describe('following users notifications', () => { slug: 'first-follower', }, { - email: 'test2@example.org', + email: 'first-follower@example.org', password: '1234', }, ) @@ -131,10 +131,27 @@ describe('following users notifications', () => { slug: 'second-follower', }, { - email: 'test3@example.org', + email: 'second-follower@example.org', password: '1234', }, ) + thirdFollower = await Factory.build( + 'user', + { + id: 'third-follower', + name: 'Third Follower', + slug: 'third-follower', + }, + { + email: 'third-follower@example.org', + password: '1234', + }, + ) + emaillessFollower = await neode.create('User', { + id: 'email-less-follower', + name: 'Email-less Follower', + slug: 'email-less-follower', + }) await secondFollower.update({ emailNotificationsFollowingUsers: false }) authenticatedUser = await firstFollower.toJson() await mutate({ @@ -146,6 +163,16 @@ describe('following users notifications', () => { mutation: followUserMutation, variables: { id: 'post-author' }, }) + authenticatedUser = await thirdFollower.toJson() + await mutate({ + mutation: followUserMutation, + variables: { id: 'post-author' }, + }) + authenticatedUser = await emaillessFollower.toJson() + await mutate({ + mutation: followUserMutation, + variables: { id: 'post-author' }, + }) jest.clearAllMocks() }) @@ -221,8 +248,43 @@ describe('following users notifications', () => { }) }) - it('sends only one email, as second follower has emails disabled', () => { - expect(sendMailMock).toHaveBeenCalledTimes(1) + it('sends notification to the email-less follower', async () => { + authenticatedUser = await emaillessFollower.toJson() + await expect( + query({ + query: notificationQuery, + }), + ).resolves.toMatchObject({ + data: { + notifications: [ + { + from: { + __typename: 'Post', + id: 'post', + }, + read: false, + reason: 'followed_user_posted', + }, + ], + }, + errors: undefined, + }) + }) + + it('sends only two emails, as second follower has emails disabled and email-less follower has no email', () => { + expect(sendMailMock).toHaveBeenCalledTimes(2) + expect(sendMailMock).toHaveBeenCalledWith( + expect.objectContaining({ + html: expect.stringContaining('Hello First Follower'), + to: 'first-follower@example.org', + }), + ) + expect(sendMailMock).toHaveBeenCalledWith( + expect.objectContaining({ + html: expect.stringContaining('Hello Third Follower'), + to: 'third-follower@example.org', + }), + ) }) }) diff --git a/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts index 7058efd25..1c7ca4c71 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts @@ -17,22 +17,23 @@ import createServer from '@src/server' CONFIG.CATEGORIES_ACTIVE = false -const sendMailMock = jest.fn() -jest.mock('../helpers/email/sendMail', () => ({ - sendMail: () => sendMailMock(), +const sendMailMock: (notification) => void = jest.fn() +jest.mock('@middleware/helpers/email/sendMail', () => ({ + sendMail: (notification) => sendMailMock(notification), })) let server, query, mutate, authenticatedUser -let postAuthor, groupMember, pendingMember, noMember +let postAuthor, groupMember, pendingMember, noMember, emaillessMember const driver = getDriver() const neode = getNeode() const mentionString = ` - @no-meber + @no-member @pending-member @group-member. + @email-less-member. ` const createPostMutation = gql` @@ -168,6 +169,12 @@ describe('mentions in groups', () => { password: '1234', }, ) + emaillessMember = await neode.create('User', { + id: 'email-less-member', + name: 'Email-less Member', + slug: 'email-less-member', + }) + authenticatedUser = await postAuthor.toJson() await mutate({ mutation: createGroupMutation(), @@ -243,6 +250,28 @@ describe('mentions in groups', () => { userId: 'pending-member', }, }) + authenticatedUser = await emaillessMember.toJson() + await mutate({ + mutation: joinGroupMutation(), + variables: { + groupId: 'public-group', + userId: 'group-member', + }, + }) + await mutate({ + mutation: joinGroupMutation(), + variables: { + groupId: 'closed-group', + userId: 'group-member', + }, + }) + await mutate({ + mutation: joinGroupMutation(), + variables: { + groupId: 'hidden-group', + userId: 'group-member', + }, + }) authenticatedUser = await postAuthor.toJson() await mutate({ mutation: changeGroupMemberRoleMutation(), @@ -260,8 +289,26 @@ describe('mentions in groups', () => { roleInGroup: 'usual', }, }) + await mutate({ + mutation: changeGroupMemberRoleMutation(), + variables: { + groupId: 'closed-group', + userId: 'email-less-member', + roleInGroup: 'usual', + }, + }) + await mutate({ + mutation: changeGroupMemberRoleMutation(), + variables: { + groupId: 'hidden-group', + userId: 'email-less-member', + roleInGroup: 'usual', + }, + }) authenticatedUser = await groupMember.toJson() await markAllAsRead() + authenticatedUser = await emaillessMember.toJson() + await markAllAsRead() }) afterEach(async () => { @@ -327,7 +374,7 @@ describe('mentions in groups', () => { __typename: 'Post', id: 'public-post', content: - 'Hey
@no-meber
@pending-member
@group-member.
! Please read this', + 'Hey
@no-member
@pending-member
@group-member.
@email-less-member.
! Please read this', }, read: false, reason: 'post_in_group', @@ -339,7 +386,7 @@ describe('mentions in groups', () => { __typename: 'Post', id: 'public-post', content: - 'Hey
@no-meber
@pending-member
@group-member.
! Please read this', + 'Hey
@no-member
@pending-member
@group-member.
@email-less-member.
! Please read this', }, read: false, reason: 'mentioned_in_post', @@ -351,7 +398,7 @@ describe('mentions in groups', () => { }) }) - it('sends 3 emails, one for each user', () => { + it('sends only 3 emails, one for each user with an email', () => { expect(sendMailMock).toHaveBeenCalledTimes(3) }) }) @@ -423,7 +470,7 @@ describe('mentions in groups', () => { __typename: 'Post', id: 'closed-post', content: - 'Hey members
@no-meber
@pending-member
@group-member.
! Please read this', + 'Hey members
@no-member
@pending-member
@group-member.
@email-less-member.
! Please read this', }, read: false, reason: 'post_in_group', @@ -435,7 +482,7 @@ describe('mentions in groups', () => { __typename: 'Post', id: 'closed-post', content: - 'Hey members
@no-meber
@pending-member
@group-member.
! Please read this', + 'Hey members
@no-member
@pending-member
@group-member.
@email-less-member.
! Please read this', }, read: false, reason: 'mentioned_in_post', @@ -519,7 +566,7 @@ describe('mentions in groups', () => { __typename: 'Post', id: 'hidden-post', content: - 'Hey hiders
@no-meber
@pending-member
@group-member.
! Please read this', + 'Hey hiders
@no-member
@pending-member
@group-member.
@email-less-member.
! Please read this', }, read: false, reason: 'post_in_group', @@ -531,7 +578,7 @@ describe('mentions in groups', () => { __typename: 'Post', id: 'hidden-post', content: - 'Hey hiders
@no-meber
@pending-member
@group-member.
! Please read this', + 'Hey hiders
@no-member
@pending-member
@group-member.
@email-less-member.
! Please read this', }, read: false, reason: 'mentioned_in_post', diff --git a/backend/src/middleware/notifications/notificationsMiddleware.observing-posts.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.observing-posts.spec.ts index 2c73d2beb..9f193eaeb 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.observing-posts.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.observing-posts.spec.ts @@ -6,15 +6,20 @@ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' import CONFIG from '@config/index' -import { cleanDatabase } from '@db/factories' +import Factory, { cleanDatabase } from '@db/factories' import { getNeode, getDriver } from '@db/neo4j' import createServer from '@src/server' CONFIG.CATEGORIES_ACTIVE = false +const sendMailMock: (notification) => void = jest.fn() +jest.mock('@middleware/helpers/email/sendMail', () => ({ + sendMail: (notification) => sendMailMock(notification), +})) + let server, query, mutate, authenticatedUser -let postAuthor, firstCommenter, secondCommenter +let postAuthor, firstCommenter, secondCommenter, emaillessObserver const driver = getDriver() const neode = getNeode() @@ -102,42 +107,47 @@ afterAll(async () => { describe('notifications for users that observe a post', () => { beforeAll(async () => { - postAuthor = await neode.create( - 'User', + postAuthor = await Factory.build( + 'user', { id: 'post-author', name: 'Post Author', slug: 'post-author', }, { - email: 'test@example.org', + email: 'post-author@example.org', password: '1234', }, ) - firstCommenter = await neode.create( - 'User', + firstCommenter = await Factory.build( + 'user', { id: 'first-commenter', name: 'First Commenter', slug: 'first-commenter', }, { - email: 'test2@example.org', + email: 'first-commenter@example.org', password: '1234', }, ) - secondCommenter = await neode.create( - 'User', + secondCommenter = await Factory.build( + 'user', { id: 'second-commenter', name: 'Second Commenter', slug: 'second-commenter', }, { - email: 'test3@example.org', + email: 'second-commenter@example.org', password: '1234', }, ) + emaillessObserver = await neode.create('User', { + id: 'email-less-observer', + name: 'Email-less Observer', + slug: 'email-less-observer', + }) authenticatedUser = await postAuthor.toJson() await mutate({ mutation: createPostMutation, @@ -147,6 +157,14 @@ describe('notifications for users that observe a post', () => { content: 'This is the content of the post', }, }) + authenticatedUser = await emaillessObserver.toJson() + await mutate({ + mutation: toggleObservePostMutation, + variables: { + id: 'post', + value: true, + }, + }) }) describe('first comment on the post', () => { @@ -198,8 +216,18 @@ describe('notifications for users that observe a post', () => { }) }) + it('sends one email', () => { + expect(sendMailMock).toHaveBeenCalledTimes(1) + expect(sendMailMock).toHaveBeenCalledWith( + expect.objectContaining({ + to: 'post-author@example.org', + }), + ) + }) + describe('second comment on post', () => { beforeAll(async () => { + jest.clearAllMocks() authenticatedUser = await secondCommenter.toJson() await mutate({ mutation: createCommentMutation, @@ -277,10 +305,25 @@ describe('notifications for users that observe a post', () => { errors: undefined, }) }) + + it('sends two emails', () => { + expect(sendMailMock).toHaveBeenCalledTimes(2) + expect(sendMailMock).toHaveBeenCalledWith( + expect.objectContaining({ + to: 'post-author@example.org', + }), + ) + expect(sendMailMock).toHaveBeenCalledWith( + expect.objectContaining({ + to: 'first-commenter@example.org', + }), + ) + }) }) describe('first commenter unfollows the post and post author comments post', () => { beforeAll(async () => { + jest.clearAllMocks() authenticatedUser = await firstCommenter.toJson() await mutate({ mutation: toggleObservePostMutation, @@ -376,6 +419,15 @@ describe('notifications for users that observe a post', () => { errors: undefined, }) }) + + it('sends one email', () => { + expect(sendMailMock).toHaveBeenCalledTimes(1) + expect(sendMailMock).toHaveBeenCalledWith( + expect.objectContaining({ + to: 'second-commenter@example.org', + }), + ) + }) }) }) }) diff --git a/backend/src/middleware/notifications/notificationsMiddleware.online-status.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.online-status.spec.ts index da9a6b250..47842029c 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.online-status.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.online-status.spec.ts @@ -13,9 +13,9 @@ import createServer from '@src/server' CONFIG.CATEGORIES_ACTIVE = false -const sendMailMock = jest.fn() -jest.mock('../helpers/email/sendMail', () => ({ - sendMail: () => sendMailMock(), +const sendMailMock: (notification) => void = jest.fn() +jest.mock('@middleware/helpers/email/sendMail', () => ({ + sendMail: (notification) => sendMailMock(notification), })) let isUserOnlineMock = jest.fn().mockReturnValue(false) diff --git a/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts index 9ca4ae7ab..6bde0aee2 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts @@ -17,14 +17,14 @@ import createServer from '@src/server' CONFIG.CATEGORIES_ACTIVE = false -const sendMailMock = jest.fn() -jest.mock('../helpers/email/sendMail', () => ({ - sendMail: () => sendMailMock(), +const sendMailMock: (notification) => void = jest.fn() +jest.mock('@middleware/helpers/email/sendMail', () => ({ + sendMail: (notification) => sendMailMock(notification), })) let server, query, mutate, authenticatedUser -let postAuthor, groupMember, pendingMember +let postAuthor, groupMember, pendingMember, emaillessMember const driver = getDriver() const neode = getNeode() @@ -159,6 +159,12 @@ describe('notify group members of new posts in group', () => { password: '1234', }, ) + emaillessMember = await neode.create('User', { + id: 'email-less-member', + name: 'Email-less Member', + slug: 'email-less-member', + }) + authenticatedUser = await postAuthor.toJson() await mutate({ mutation: createGroupMutation(), @@ -186,6 +192,14 @@ describe('notify group members of new posts in group', () => { userId: 'pending-member', }, }) + authenticatedUser = await emaillessMember.toJson() + await mutate({ + mutation: joinGroupMutation(), + variables: { + groupId: 'g-1', + userId: 'group-member', + }, + }) authenticatedUser = await postAuthor.toJson() await mutate({ mutation: changeGroupMemberRoleMutation(), @@ -195,6 +209,14 @@ describe('notify group members of new posts in group', () => { roleInGroup: 'usual', }, }) + await mutate({ + mutation: changeGroupMemberRoleMutation(), + variables: { + groupId: 'g-1', + userId: 'email-less-member', + roleInGroup: 'usual', + }, + }) }) afterEach(async () => { diff --git a/backend/src/middleware/notifications/notificationsMiddleware.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.spec.ts index 908ccac22..31e458e2a 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.spec.ts @@ -18,9 +18,9 @@ import { leaveGroupMutation } from '@graphql/queries/leaveGroupMutation' import { removeUserFromGroupMutation } from '@graphql/queries/removeUserFromGroupMutation' import createServer, { pubsub } from '@src/server' -const sendMailMock = jest.fn() -jest.mock('../helpers/email/sendMail', () => ({ - sendMail: () => sendMailMock(), +const sendMailMock: (notification) => void = jest.fn() +jest.mock('@middleware/helpers/email/sendMail', () => ({ + sendMail: (notification) => sendMailMock(notification), })) const chatMessageTemplateMock = jest.fn() @@ -195,8 +195,8 @@ describe('notifications', () => { beforeEach(async () => { jest.clearAllMocks() commentContent = 'Commenters comment.' - commentAuthor = await neode.create( - 'User', + commentAuthor = await Factory.build( + 'user', { id: 'commentAuthor', name: 'Mrs Comment', @@ -345,8 +345,8 @@ describe('notifications', () => { beforeEach(async () => { jest.clearAllMocks() - postAuthor = await neode.create( - 'User', + postAuthor = await Factory.build( + 'user', { id: 'postAuthor', name: 'Mrs Post', @@ -658,8 +658,8 @@ describe('notifications', () => { beforeEach(async () => { commentContent = 'One mention about me with @al-capone.' - commentAuthor = await neode.create( - 'User', + commentAuthor = await Factory.build( + 'user', { id: 'commentAuthor', name: 'Mrs Comment', @@ -673,15 +673,15 @@ describe('notifications', () => { }) it('sends only one notification with reason mentioned_in_comment', async () => { - postAuthor = await neode.create( - 'User', + postAuthor = await Factory.build( + 'user', { id: 'MrPostAuthor', name: 'Mr Author', slug: 'mr-author', }, { - email: 'post-author@example.org', + email: 'post-author2@example.org', password: '1234', }, ) @@ -756,8 +756,8 @@ describe('notifications', () => { await postAuthor.relateTo(notifiedUser, 'blocked') commentContent = 'One mention about me with @al-capone.' - commentAuthor = await neode.create( - 'User', + commentAuthor = await Factory.build( + 'user', { id: 'commentAuthor', name: 'Mrs Comment', @@ -807,8 +807,8 @@ describe('notifications', () => { await postAuthor.relateTo(notifiedUser, 'muted') commentContent = 'One mention about me with @al-capone.' - commentAuthor = await neode.create( - 'User', + commentAuthor = await Factory.build( + 'user', { id: 'commentAuthor', name: 'Mrs Comment', @@ -879,8 +879,8 @@ describe('notifications', () => { beforeEach(async () => { jest.clearAllMocks() - chatSender = await neode.create( - 'User', + chatSender = await Factory.build( + 'user', { id: 'chatSender', name: 'chatSender', @@ -931,7 +931,7 @@ describe('notifications', () => { content: 'Some nice message to chatReceiver', senderId: 'chatSender', username: 'chatSender', - avatar: null, + avatar: expect.any(String), date: expect.any(String), saved: true, distributed: false, @@ -967,7 +967,7 @@ describe('notifications', () => { content: 'Some nice message to chatReceiver', senderId: 'chatSender', username: 'chatSender', - avatar: null, + avatar: expect.any(String), date: expect.any(String), saved: true, distributed: false, @@ -1046,7 +1046,7 @@ describe('notifications', () => { content: 'Some nice message to chatReceiver', senderId: 'chatSender', username: 'chatSender', - avatar: null, + avatar: expect.any(String), date: expect.any(String), saved: true, distributed: false, diff --git a/backend/src/middleware/notifications/notificationsMiddleware.ts b/backend/src/middleware/notifications/notificationsMiddleware.ts index f7be031c8..b9f5c4284 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.ts @@ -18,59 +18,28 @@ import { pubsub, NOTIFICATION_ADDED, ROOM_COUNT_UPDATED, CHAT_MESSAGE_ADDED } fr import extractMentionedUsers from './mentions/extractMentionedUsers' -const queryNotificationEmails = async (context, notificationUserIds) => { - if (!notificationUserIds?.length) return [] - const userEmailCypher = ` - MATCH (user: User) - // blocked users are filtered out from notifications already - WHERE user.id in $notificationUserIds - WITH user - MATCH (user)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress) - RETURN emailAddress {.email} - ` - const session = context.driver.session() - const writeTxResultPromise = session.readTransaction(async (transaction) => { - const emailAddressTransactionResponse = await transaction.run(userEmailCypher, { - notificationUserIds, - }) - return emailAddressTransactionResponse.records.map((record) => record.get('emailAddress')) - }) - try { - const emailAddresses = await writeTxResultPromise - return emailAddresses - } catch (error) { - throw new Error(error) - } finally { - session.close() - } -} - const publishNotifications = async ( context, - promises, + notificationsPromise, emailNotificationSetting: string, emailsSent: string[] = [], ): Promise => { - let notifications = await Promise.all(promises) - notifications = notifications.flat() - const notificationsEmailAddresses = await queryNotificationEmails( - context, - notifications.map((notification) => notification.to.id), - ) - notifications.forEach((notificationAdded, index) => { + const notifications = await notificationsPromise + notifications.forEach((notificationAdded) => { pubsub.publish(NOTIFICATION_ADDED, { notificationAdded }) if ( + notificationAdded.email && // no primary email was found (notificationAdded.to[emailNotificationSetting] ?? true) && !isUserOnline(notificationAdded.to) && - !emailsSent.includes(notificationsEmailAddresses[index].email) + !emailsSent.includes(notificationAdded.email) ) { sendMail( notificationTemplate({ - email: notificationsEmailAddresses[index].email, + email: notificationAdded.email, variables: { notification: notificationAdded }, }), ) - emailsSent.push(notificationsEmailAddresses[index].email) + emailsSent.push(notificationAdded.email) } }) return emailsSent @@ -82,7 +51,7 @@ const handleJoinGroup = async (resolve, root, args, context, resolveInfo) => { if (user) { await publishNotifications( context, - [notifyOwnersOfGroup(groupId, userId, 'user_joined_group', context)], + notifyOwnersOfGroup(groupId, userId, 'user_joined_group', context), 'emailNotificationsGroupMemberJoined', ) } @@ -95,7 +64,7 @@ const handleLeaveGroup = async (resolve, root, args, context, resolveInfo) => { if (user) { await publishNotifications( context, - [notifyOwnersOfGroup(groupId, userId, 'user_left_group', context)], + notifyOwnersOfGroup(groupId, userId, 'user_left_group', context), 'emailNotificationsGroupMemberLeft', ) } @@ -108,7 +77,7 @@ const handleChangeGroupMemberRole = async (resolve, root, args, context, resolve if (user) { await publishNotifications( context, - [notifyMemberOfGroup(groupId, userId, 'changed_group_member_role', context)], + notifyMemberOfGroup(groupId, userId, 'changed_group_member_role', context), 'emailNotificationsGroupMemberRoleChanged', ) } @@ -121,7 +90,7 @@ const handleRemoveUserFromGroup = async (resolve, root, args, context, resolveIn if (user) { await publishNotifications( context, - [notifyMemberOfGroup(groupId, userId, 'removed_user_from_group', context)], + notifyMemberOfGroup(groupId, userId, 'removed_user_from_group', context), 'emailNotificationsGroupMemberRemoved', ) } @@ -135,20 +104,20 @@ const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo if (post) { const sentEmails: string[] = await publishNotifications( context, - [notifyUsersOfMention('Post', post.id, idsOfUsers, 'mentioned_in_post', context)], + notifyUsersOfMention('Post', post.id, idsOfUsers, 'mentioned_in_post', context), 'emailNotificationsMention', ) sentEmails.concat( await publishNotifications( context, - [notifyFollowingUsers(post.id, groupId, context)], + notifyFollowingUsers(post.id, groupId, context), 'emailNotificationsFollowingUsers', sentEmails, ), ) await publishNotifications( context, - [notifyGroupMembersOfNewPost(post.id, groupId, context)], + notifyGroupMembersOfNewPost(post.id, groupId, context), 'emailNotificationsPostInGroup', sentEmails, ) @@ -164,20 +133,18 @@ const handleContentDataOfComment = async (resolve, root, args, context, resolveI idsOfMentionedUsers = idsOfMentionedUsers.filter((id) => id !== postAuthor.id) const sentEmails: string[] = await publishNotifications( context, - [ - notifyUsersOfMention( - 'Comment', - comment.id, - idsOfMentionedUsers, - 'mentioned_in_comment', - context, - ), - ], + notifyUsersOfMention( + 'Comment', + comment.id, + idsOfMentionedUsers, + 'mentioned_in_comment', + context, + ), 'emailNotificationsMention', ) await publishNotifications( context, - [notifyUsersOfComment('Comment', comment.id, 'commented_on_post', context)], + notifyUsersOfComment('Comment', comment.id, 'commented_on_post', context), 'emailNotificationsCommentOnObservedPost', sentEmails, ) @@ -208,17 +175,20 @@ const notifyFollowingUsers = async (postId, groupId, context) => { const cypher = ` MATCH (post:Post { id: $postId })<-[:WROTE]-(author:User { id: $userId })<-[:FOLLOWS]-(user:User) OPTIONAL MATCH (post)-[:IN]->(group:Group { id: $groupId }) - WITH post, author, user, group WHERE group IS NULL OR group.groupType = 'public' + OPTIONAL MATCH (user)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress) + WITH post, author, user, emailAddress, group + WHERE group IS NULL OR group.groupType = 'public' MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user) SET notification.read = FALSE SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime())) SET notification.updatedAt = toString(datetime()) - WITH notification, author, user, + WITH notification, author, user, emailAddress.email as email, post {.*, author: properties(author) } AS finalResource RETURN notification { .*, from: finalResource, to: properties(user), + email: email, relatedUser: properties(author) } ` @@ -233,8 +203,7 @@ const notifyFollowingUsers = async (postId, groupId, context) => { return notificationTransactionResponse.records.map((record) => record.get('notification')) }) try { - const notifications = await writeTxResultPromise - return notifications + return await writeTxResultPromise } catch (error) { throw new Error(error) } finally { @@ -247,23 +216,25 @@ const notifyGroupMembersOfNewPost = async (postId, groupId, context) => { const reason = 'post_in_group' const cypher = ` MATCH (post:Post { id: $postId })<-[:WROTE]-(author:User { id: $userId }) + OPTIONAL MATCH (user)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress) MATCH (post)-[:IN]->(group:Group { id: $groupId })<-[membership:MEMBER_OF]-(user:User) WHERE NOT membership.role = 'pending' AND NOT (user)-[:MUTED]->(group) AND NOT (user)-[:MUTED]->(author) AND NOT (user)-[:BLOCKED]-(author) AND NOT user.id = $userId - WITH post, author, user + WITH post, author, user, emailAddress MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user) SET notification.read = FALSE SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime())) SET notification.updatedAt = toString(datetime()) - WITH notification, author, user, + WITH notification, author, user, emailAddress.email as email, post {.*, author: properties(author) } AS finalResource RETURN notification { .*, from: finalResource, to: properties(user), + email: email, relatedUser: properties(author) } ` @@ -278,8 +249,7 @@ const notifyGroupMembersOfNewPost = async (postId, groupId, context) => { return notificationTransactionResponse.records.map((record) => record.get('notification')) }) try { - const notifications = await writeTxResultPromise - return notifications + return await writeTxResultPromise } catch (error) { throw new Error(error) } finally { @@ -295,12 +265,13 @@ const notifyOwnersOfGroup = async (groupId, userId, reason, context) => { WITH owner, group, user, membership MERGE (group)-[notification:NOTIFIED {reason: $reason}]->(owner) WITH group, owner, notification, user, membership + OPTIONAL MATCH (owner)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress) SET notification.read = FALSE SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime())) SET notification.updatedAt = toString(datetime()) SET notification.relatedUserId = $userId - WITH owner, group { __typename: 'Group', .*, myRole: membership.roleInGroup } AS finalGroup, user, notification - RETURN notification {.*, from: finalGroup, to: properties(owner), relatedUser: properties(user) } + WITH owner, emailAddress.email as email, group { __typename: 'Group', .*, myRole: membership.roleInGroup } AS finalGroup, user, notification + RETURN notification {.*, from: finalGroup, to: properties(owner), email: email, relatedUser: properties(user) } ` const session = context.driver.session() const writeTxResultPromise = session.writeTransaction(async (transaction) => { @@ -312,8 +283,7 @@ const notifyOwnersOfGroup = async (groupId, userId, reason, context) => { return notificationTransactionResponse.records.map((record) => record.get('notification')) }) try { - const notifications = await writeTxResultPromise - return notifications + return await writeTxResultPromise } catch (error) { throw new Error(error) } finally { @@ -327,17 +297,18 @@ const notifyMemberOfGroup = async (groupId, userId, reason, context) => { MATCH (owner:User { id: $ownerId }) MATCH (user:User { id: $userId }) MATCH (group:Group { id: $groupId }) + OPTIONAL MATCH (user)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress) OPTIONAL MATCH (user)-[membership:MEMBER_OF]->(group) - WITH user, group, owner, membership + WITH user, group, owner, membership, emailAddress MERGE (group)-[notification:NOTIFIED {reason: $reason}]->(user) - WITH group, user, notification, owner, membership + WITH group, user, notification, owner, membership, emailAddress SET notification.read = FALSE SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime())) SET notification.updatedAt = toString(datetime()) SET notification.relatedUserId = $ownerId WITH group { __typename: 'Group', .*, myRole: membership.roleInGroup } AS finalGroup, - notification, user, owner - RETURN notification {.*, from: finalGroup, to: properties(user), relatedUser: properties(owner) } + notification, user, emailAddress.email as email, owner + RETURN notification {.*, from: finalGroup, to: properties(user), email: email, relatedUser: properties(owner) } ` const session = context.driver.session() const writeTxResultPromise = session.writeTransaction(async (transaction) => { @@ -350,8 +321,7 @@ const notifyMemberOfGroup = async (groupId, userId, reason, context) => { return notificationTransactionResponse.records.map((record) => record.get('notification')) }) try { - const notifications = await writeTxResultPromise - return notifications + return await writeTxResultPromise } catch (error) { throw new Error(error) } finally { @@ -371,11 +341,13 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => { WHERE user.id in $idsOfUsers AND NOT (user)-[:BLOCKED]-(author) AND NOT (user)-[:MUTED]->(author) + OPTIONAL MATCH (user)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress) OPTIONAL MATCH (post)-[:IN]->(group:Group) OPTIONAL MATCH (group)<-[membership:MEMBER_OF]-(user) - WITH post, author, user, group WHERE group IS NULL OR group.groupType = 'public' OR membership.role IN ['usual', 'admin', 'owner'] + WITH post, author, user, group, emailAddress + WHERE group IS NULL OR group.groupType = 'public' OR membership.role IN ['usual', 'admin', 'owner'] MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user) - WITH post AS resource, notification, user + WITH post AS resource, notification, user, emailAddress ` break } @@ -388,25 +360,27 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => { AND NOT (user)-[:BLOCKED]-(postAuthor) AND NOT (user)-[:MUTED]->(commenter) AND NOT (user)-[:MUTED]->(postAuthor) + OPTIONAL MATCH (user)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress) OPTIONAL MATCH (post)-[:IN]->(group:Group) OPTIONAL MATCH (group)<-[membership:MEMBER_OF]-(user) - WITH comment, user, group WHERE group IS NULL OR group.groupType = 'public' OR membership.role IN ['usual', 'admin', 'owner'] + WITH comment, user, group, emailAddress + WHERE group IS NULL OR group.groupType = 'public' OR membership.role IN ['usual', 'admin', 'owner'] MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user) - WITH comment AS resource, notification, user + WITH comment AS resource, notification, user, emailAddress ` break } } mentionedCypher += ` - WITH notification, user, resource, + WITH notification, user, resource, emailAddress, [(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors, [(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] AS posts - WITH resource, user, notification, authors, posts, + WITH resource, user, emailAddress.email as email, notification, authors, posts, resource {.*, __typename: [l IN labels(resource) WHERE l IN ['Post', 'Comment', 'Group']][0], author: authors[0], post: posts[0]} AS finalResource SET notification.read = FALSE SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime())) SET notification.updatedAt = toString(datetime()) - RETURN notification {.*, from: finalResource, to: properties(user), relatedUser: properties(user) } + RETURN notification {.*, from: finalResource, to: properties(user), email: email, relatedUser: properties(user) } ` const session = context.driver.session() const writeTxResultPromise = session.writeTransaction(async (transaction) => { @@ -418,8 +392,7 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => { return notificationTransactionResponse.records.map((record) => record.get('notification')) }) try { - const notifications = await writeTxResultPromise - return notifications + return await writeTxResultPromise } catch (error) { throw new Error(error) } finally { @@ -437,18 +410,20 @@ const notifyUsersOfComment = async (label, commentId, reason, context) => { WHERE NOT (observingUser)-[:BLOCKED]-(commenter) AND NOT (observingUser)-[:MUTED]->(commenter) AND NOT observingUser.id = $userId - WITH observingUser, post, comment, commenter + OPTIONAL MATCH (observingUser)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress) + WITH observingUser, emailAddress, post, comment, commenter MATCH (postAuthor:User)-[:WROTE]->(post) MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(observingUser) SET notification.read = FALSE SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime())) SET notification.updatedAt = toString(datetime()) - WITH notification, observingUser, post, commenter, postAuthor, + WITH notification, observingUser, emailAddress.email as email, post, commenter, postAuthor, comment {.*, __typename: labels(comment)[0], author: properties(commenter), post: post {.*, author: properties(postAuthor) } } AS finalResource RETURN notification { .*, from: finalResource, to: properties(observingUser), + email: email, relatedUser: properties(commenter) } `, @@ -461,8 +436,7 @@ const notifyUsersOfComment = async (label, commentId, reason, context) => { return notificationTransactionResponse.records.map((record) => record.get('notification')) }) try { - const notifications = await writeTxResultPromise - return notifications + return await writeTxResultPromise } finally { session.close() } From d4cc843662eb0b0ac356f01793073142487764d5 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 24 Apr 2025 21:50:13 +0200 Subject: [PATCH 33/54] lint n/no-sync (#8405) --- backend/.eslintrc.cjs | 4 ++-- backend/src/db/admin.ts | 1 + backend/src/db/factories.ts | 1 + .../20200312140328-bulk_upload_to_s3.ts | 1 + .../20200326160326-remove_dangling_image_urls.ts | 1 + backend/src/helpers/encryptPassword.ts | 1 + .../src/middleware/helpers/email/templates/de/index.ts | 1 + .../src/middleware/helpers/email/templates/en/index.ts | 1 + backend/src/middleware/helpers/email/templates/index.ts | 1 + backend/src/schema/resolvers/embeds.spec.ts | 3 +++ backend/src/schema/resolvers/embeds/findProvider.ts | 1 + backend/src/schema/resolvers/images/images.ts | 1 + backend/src/schema/resolvers/passwordReset.ts | 2 +- backend/src/schema/resolvers/user_management.ts | 8 ++++---- 14 files changed, 20 insertions(+), 7 deletions(-) diff --git a/backend/.eslintrc.cjs b/backend/.eslintrc.cjs index 51adb6831..1fe6b8779 100644 --- a/backend/.eslintrc.cjs +++ b/backend/.eslintrc.cjs @@ -115,7 +115,7 @@ module.exports = { 'n/no-callback-literal': 'error', // 'n/no-deprecated-api': 'error', // part of n/recommended // 'n/no-exports-assign': 'error', // part of n/recommended - 'n/no-extraneous-import': 'off', // TODO // part of n/recommended + 'n/no-extraneous-import': 'off', // duplicate of import/no-extraneous-dependencies // part of n/recommended // 'n/no-extraneous-require': 'error', // part of n/recommended 'n/no-hide-core-modules': 'error', 'n/no-missing-import': 'off', // not compatible with typescript // part of n/recommended @@ -127,7 +127,7 @@ module.exports = { // 'n/no-process-exit': 'error', // part of n/recommended 'n/no-restricted-import': 'error', 'n/no-restricted-require': 'error', - // 'n/no-sync': 'error', + 'n/no-sync': 'error', // 'n/no-unpublished-bin': 'error', // part of n/recommended 'n/no-unpublished-import': [ 'error', diff --git a/backend/src/db/admin.ts b/backend/src/db/admin.ts index ae6bca49e..b60eeb38a 100644 --- a/backend/src/db/admin.ts +++ b/backend/src/db/admin.ts @@ -11,6 +11,7 @@ import { getDriver } from './neo4j' const defaultAdmin = { email: 'admin@example.org', + // eslint-disable-next-line n/no-sync password: hashSync('1234', 10), name: 'admin', id: uuid(), diff --git a/backend/src/db/factories.ts b/backend/src/db/factories.ts index a0230a467..83f4cab75 100644 --- a/backend/src/db/factories.ts +++ b/backend/src/db/factories.ts @@ -95,6 +95,7 @@ Factory.define('basicUser') return slug || slugify(name, { lower: true }) }) .attr('encryptedPassword', ['password'], (password) => { + // eslint-disable-next-line n/no-sync return hashSync(password, 10) }) diff --git a/backend/src/db/migrations-examples/20200312140328-bulk_upload_to_s3.ts b/backend/src/db/migrations-examples/20200312140328-bulk_upload_to_s3.ts index 9d97aab7e..bdbd459b3 100644 --- a/backend/src/db/migrations-examples/20200312140328-bulk_upload_to_s3.ts +++ b/backend/src/db/migrations-examples/20200312140328-bulk_upload_to_s3.ts @@ -55,6 +55,7 @@ export async function up(next) { const { pathname } = new URL(url, 'http://example.org') const fileLocation = path.join(__dirname, `../../../public/${pathname}`) const s3Location = `original${pathname}` + // eslint-disable-next-line n/no-sync if (existsSync(fileLocation)) { const mimeType = mime.lookup(fileLocation) const params = { diff --git a/backend/src/db/migrations-examples/20200326160326-remove_dangling_image_urls.ts b/backend/src/db/migrations-examples/20200326160326-remove_dangling_image_urls.ts index f5fbd24de..6f2d26c37 100644 --- a/backend/src/db/migrations-examples/20200326160326-remove_dangling_image_urls.ts +++ b/backend/src/db/migrations-examples/20200326160326-remove_dangling_image_urls.ts @@ -33,6 +33,7 @@ export async function up(next) { const urls = records.map((record) => record.get('url')) const danglingUrls = urls.filter((url) => { const fileLocation = `public${url}` + // eslint-disable-next-line n/no-sync return !existsSync(fileLocation) }) await transaction.run( diff --git a/backend/src/helpers/encryptPassword.ts b/backend/src/helpers/encryptPassword.ts index d8d08c23c..1d12556ea 100644 --- a/backend/src/helpers/encryptPassword.ts +++ b/backend/src/helpers/encryptPassword.ts @@ -4,6 +4,7 @@ import { hashSync } from 'bcryptjs' export default function (args) { + // eslint-disable-next-line n/no-sync args.encryptedPassword = hashSync(args.password, 10) delete args.password return args diff --git a/backend/src/middleware/helpers/email/templates/de/index.ts b/backend/src/middleware/helpers/email/templates/de/index.ts index 408bbb34d..4aa323b9f 100644 --- a/backend/src/middleware/helpers/email/templates/de/index.ts +++ b/backend/src/middleware/helpers/email/templates/de/index.ts @@ -3,6 +3,7 @@ import fs from 'node:fs' import path from 'node:path' +// eslint-disable-next-line n/no-sync const readFile = (fileName) => fs.readFileSync(path.join(__dirname, fileName), 'utf-8') export const notification = readFile('./notification.html') diff --git a/backend/src/middleware/helpers/email/templates/en/index.ts b/backend/src/middleware/helpers/email/templates/en/index.ts index 408bbb34d..4aa323b9f 100644 --- a/backend/src/middleware/helpers/email/templates/en/index.ts +++ b/backend/src/middleware/helpers/email/templates/en/index.ts @@ -3,6 +3,7 @@ import fs from 'node:fs' import path from 'node:path' +// eslint-disable-next-line n/no-sync const readFile = (fileName) => fs.readFileSync(path.join(__dirname, fileName), 'utf-8') export const notification = readFile('./notification.html') diff --git a/backend/src/middleware/helpers/email/templates/index.ts b/backend/src/middleware/helpers/email/templates/index.ts index f481516db..9a64192ce 100644 --- a/backend/src/middleware/helpers/email/templates/index.ts +++ b/backend/src/middleware/helpers/email/templates/index.ts @@ -3,6 +3,7 @@ import fs from 'node:fs' import path from 'node:path' +// eslint-disable-next-line n/no-sync const readFile = (fileName) => fs.readFileSync(path.join(__dirname, fileName), 'utf-8') export const signup = readFile('./signup.html') diff --git a/backend/src/schema/resolvers/embeds.spec.ts b/backend/src/schema/resolvers/embeds.spec.ts index a3b33f81f..f6de4d13e 100644 --- a/backend/src/schema/resolvers/embeds.spec.ts +++ b/backend/src/schema/resolvers/embeds.spec.ts @@ -20,14 +20,17 @@ afterEach(() => { let variables = {} +// eslint-disable-next-line n/no-sync const HumanConnectionOrg = fs.readFileSync( path.join(__dirname, '../../../snapshots/embeds/HumanConnectionOrg.html'), 'utf8', ) +// eslint-disable-next-line n/no-sync const pr3934 = fs.readFileSync( path.join(__dirname, '../../../snapshots/embeds/pr3934.html'), 'utf8', ) +// eslint-disable-next-line n/no-sync const babyLovesCat = fs.readFileSync( path.join(__dirname, '../../../snapshots/embeds/babyLovesCat.html'), 'utf8', diff --git a/backend/src/schema/resolvers/embeds/findProvider.ts b/backend/src/schema/resolvers/embeds/findProvider.ts index 9be9732bf..6f5e0df90 100644 --- a/backend/src/schema/resolvers/embeds/findProvider.ts +++ b/backend/src/schema/resolvers/embeds/findProvider.ts @@ -8,6 +8,7 @@ import path from 'node:path' import { minimatch } from 'minimatch' +// eslint-disable-next-line n/no-sync let oEmbedProvidersFile = fs.readFileSync( path.join(__dirname, '../../../../public/providers.json'), 'utf8', diff --git a/backend/src/schema/resolvers/images/images.ts b/backend/src/schema/resolvers/images/images.ts index d8ce03758..11532347d 100644 --- a/backend/src/schema/resolvers/images/images.ts +++ b/backend/src/schema/resolvers/images/images.ts @@ -152,6 +152,7 @@ const s3Upload = async ({ createReadStream, uniqueFilename, mimetype }) => { const localFileDelete = async (url) => { const location = `public${url}` + // eslint-disable-next-line n/no-sync if (existsSync(location)) unlinkSync(location) } diff --git a/backend/src/schema/resolvers/passwordReset.ts b/backend/src/schema/resolvers/passwordReset.ts index ab53d65fa..6e74ac710 100644 --- a/backend/src/schema/resolvers/passwordReset.ts +++ b/backend/src/schema/resolvers/passwordReset.ts @@ -21,7 +21,7 @@ export default { resetPassword: async (_parent, { email, nonce, newPassword }, { driver }) => { const stillValid = new Date() stillValid.setDate(stillValid.getDate() - 1) - const encryptedNewPassword = await bcrypt.hashSync(newPassword, 10) + const encryptedNewPassword = await bcrypt.hash(newPassword, 10) const session = driver.session() try { const passwordResetTxPromise = session.writeTransaction(async (transaction) => { diff --git a/backend/src/schema/resolvers/user_management.ts b/backend/src/schema/resolvers/user_management.ts index 13437e815..072755850 100644 --- a/backend/src/schema/resolvers/user_management.ts +++ b/backend/src/schema/resolvers/user_management.ts @@ -44,7 +44,7 @@ export default { const [currentUser] = await loginReadTxResultPromise if ( currentUser && - (await bcrypt.compareSync(password, currentUser.encryptedPassword)) && + (await bcrypt.compare(password, currentUser.encryptedPassword)) && !currentUser.disabled ) { delete currentUser.encryptedPassword @@ -62,15 +62,15 @@ export default { const currentUser = await neode.find('User', user.id) const encryptedPassword = currentUser.get('encryptedPassword') - if (!(await bcrypt.compareSync(oldPassword, encryptedPassword))) { + if (!(await bcrypt.compare(oldPassword, encryptedPassword))) { throw new AuthenticationError('Old password is not correct') } - if (await bcrypt.compareSync(newPassword, encryptedPassword)) { + if (await bcrypt.compare(newPassword, encryptedPassword)) { throw new AuthenticationError('Old password and new password should be different') } - const newEncryptedPassword = await bcrypt.hashSync(newPassword, 10) + const newEncryptedPassword = await bcrypt.hash(newPassword, 10) await currentUser.update({ encryptedPassword: newEncryptedPassword, updatedAt: new Date().toISOString(), From 2369d13ca2c702a2786d028466401cba6d285a83 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 24 Apr 2025 22:39:32 +0200 Subject: [PATCH 34/54] lint everything, disable some setup steps for jest (#8423) --- backend/.eslintignore | 3 +++ backend/package.json | 2 +- backend/test/setup.ts | 10 ++++++---- 3 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 backend/.eslintignore diff --git a/backend/.eslintignore b/backend/.eslintignore new file mode 100644 index 000000000..e19e2338d --- /dev/null +++ b/backend/.eslintignore @@ -0,0 +1,3 @@ +node_modules/ +build/ +coverage/ \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index 60ecba12e..7137d5c25 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,7 +12,7 @@ "build": "tsc && tsc-alias && ./scripts/build.copy.files.sh", "dev": "nodemon --exec ts-node --require tsconfig-paths/register src/ -e js,ts,gql", "dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/ -e js,ts,gql", - "lint": "eslint --max-warnings=0 --ext .js,.ts ./src", + "lint": "eslint --max-warnings=0 --ext .js,.ts .", "test": "cross-env NODE_ENV=test NODE_OPTIONS=--max-old-space-size=8192 jest --runInBand --coverage --forceExit --detectOpenHandles", "db:reset": "ts-node --require tsconfig-paths/register src/db/reset.ts", "db:reset:withmigrations": "ts-node --require tsconfig-paths/register src/db/reset-with-migrations.ts", diff --git a/backend/test/setup.ts b/backend/test/setup.ts index d2f24bd40..594b9763d 100644 --- a/backend/test/setup.ts +++ b/backend/test/setup.ts @@ -1,8 +1,10 @@ // Polyfill missing encoders in jsdom // https://stackoverflow.com/questions/68468203/why-am-i-getting-textencoder-is-not-defined-in-jest -import { TextEncoder, TextDecoder } from 'util' -global.TextEncoder = TextEncoder -global.TextDecoder = TextDecoder as any +// import { TextEncoder, TextDecoder } from 'util' + +// global.TextEncoder = TextEncoder +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment +// global.TextDecoder = TextDecoder as any // Metascraper takes longer nowadays, double time -jest.setTimeout(10000) \ No newline at end of file +// jest.setTimeout(10000) From 507179738a5a86cefd82114086333c1eb4201984 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 25 Apr 2025 10:04:58 +0200 Subject: [PATCH 35/54] refactor(backend): types for neo4j & neode (#8409) * type for neo4j and neode * fix build * remove flakyness * wait for neode to install schema * remove flakyness * explain why we wait for a non-promise --- backend/src/db/badges.ts | 2 +- backend/src/db/factories.ts | 2 +- backend/src/db/migrate/store.ts | 13 ++++--- backend/src/db/migrate/template.ts | 4 +-- ...123150105-merge_duplicate_user_accounts.ts | 3 +- ...23150110-merge_duplicate_location_nodes.ts | 3 +- ..._between_existing_blocked_relationships.ts | 8 ++--- .../20200207080200-fulltext_index_for_tags.ts | 4 +-- ...213230248-add_unique_index_to_image_url.ts | 4 +-- .../20200312140328-bulk_upload_to_s3.ts | 4 +-- ...15-refactor_all_images_to_separate_type.ts | 4 +-- ...emove_deleted_users_obsolete_attributes.ts | 4 +-- ...emove_deleted_posts_obsolete_attributes.ts | 4 +-- ...200326160326-remove_dangling_image_urls.ts | 2 +- ...1614023644903-add-clickedCount-to-posts.ts | 4 +-- ...77130817-add-viewedTeaserCount-to-posts.ts | 4 +-- .../20210506150512-add-donations-node.ts | 4 +-- ...otificationEmails-property-to-all-users.ts | 4 +-- ...text_indices_and_unique_keys_for_groups.ts | 4 +-- .../20230320130345-fulltext-search-indexes.ts | 4 +-- .../20230329150329-article-label-for-posts.ts | 4 +-- .../20230608130637-add-postType-property.ts | 4 +-- .../20231017141022-fix-event-dates.ts | 9 ++--- ...20250331130323-author-observes-own-post.ts | 4 +-- .../20250331140313-commenter-observes-post.ts | 4 +-- ...50405030454-email-notification-settings.ts | 4 +-- .../20250414220436-delete-old-badges.ts | 4 +-- backend/src/db/neo4j.ts | 6 ++-- backend/src/db/seed.ts | 2 +- backend/src/jwt/decode.spec.ts | 17 +++++---- .../hashtags/hashtagsMiddleware.spec.ts | 19 ++++------ .../middleware/languages/languages.spec.ts | 2 +- .../notificationsMiddleware.emails.spec.ts | 2 +- ...ficationsMiddleware.followed-users.spec.ts | 2 +- ...tionsMiddleware.mentions-in-groups.spec.ts | 2 +- ...icationsMiddleware.observing-posts.spec.ts | 2 +- ...ificationsMiddleware.online-status.spec.ts | 2 +- ...icationsMiddleware.posts-in-groups.spec.ts | 2 +- .../notificationsMiddleware.spec.ts | 2 +- .../src/middleware/orderByMiddleware.spec.ts | 2 +- .../middleware/permissionsMiddleware.spec.ts | 2 +- .../src/middleware/permissionsMiddleware.ts | 8 +++-- .../src/middleware/slugifyMiddleware.spec.ts | 2 +- .../softDelete/softDeleteMiddleware.spec.ts | 2 +- .../src/middleware/userInteractions.spec.ts | 2 +- .../validation/validationMiddleware.spec.ts | 2 +- backend/src/models/User.spec.ts | 3 +- backend/src/schema/resolvers/badges.spec.ts | 2 +- backend/src/schema/resolvers/comments.spec.ts | 2 +- .../src/schema/resolvers/donations.spec.ts | 2 +- backend/src/schema/resolvers/emails.spec.ts | 31 ++++++++++------ .../src/schema/resolvers/filter-posts.spec.ts | 2 +- backend/src/schema/resolvers/follow.spec.ts | 2 +- backend/src/schema/resolvers/follow.ts | 1 + backend/src/schema/resolvers/groups.spec.ts | 2 +- .../schema/resolvers/images/images.spec.ts | 27 ++++++++------ backend/src/schema/resolvers/images/images.ts | 2 +- .../src/schema/resolvers/inviteCodes.spec.ts | 2 +- .../src/schema/resolvers/locations.spec.ts | 2 +- backend/src/schema/resolvers/messages.spec.ts | 2 +- .../src/schema/resolvers/moderation.spec.ts | 4 +-- .../schema/resolvers/notifications.spec.ts | 2 +- .../src/schema/resolvers/observePosts.spec.ts | 2 +- .../schema/resolvers/passwordReset.spec.ts | 3 +- backend/src/schema/resolvers/posts.spec.ts | 24 +++++++++---- .../schema/resolvers/postsInGroups.spec.ts | 2 +- .../src/schema/resolvers/registration.spec.ts | 36 +++++++++++++------ backend/src/schema/resolvers/reports.spec.ts | 2 +- backend/src/schema/resolvers/rooms.spec.ts | 2 +- backend/src/schema/resolvers/searches.spec.ts | 2 +- backend/src/schema/resolvers/shout.spec.ts | 2 +- .../src/schema/resolvers/socialMedia.spec.ts | 2 +- backend/src/schema/resolvers/socialMedia.ts | 1 + .../src/schema/resolvers/statistics.spec.ts | 2 +- backend/src/schema/resolvers/userData.spec.ts | 2 +- .../schema/resolvers/user_management.spec.ts | 30 +++++++++------- .../src/schema/resolvers/user_management.ts | 2 +- backend/src/schema/resolvers/users.spec.ts | 9 ++--- .../schema/resolvers/users/location.spec.ts | 3 +- .../schema/resolvers/users/mutedUsers.spec.ts | 2 +- .../resolvers/viewedTeaserCount.spec.ts | 2 +- 81 files changed, 236 insertions(+), 180 deletions(-) diff --git a/backend/src/db/badges.ts b/backend/src/db/badges.ts index b4e879357..3fbbd4d7f 100644 --- a/backend/src/db/badges.ts +++ b/backend/src/db/badges.ts @@ -12,6 +12,6 @@ import { trophies, verification } from './seed/badges' await trophies() await verification() } finally { - await neode.close() + neode.close() } })() diff --git a/backend/src/db/factories.ts b/backend/src/db/factories.ts index 83f4cab75..e951c3839 100644 --- a/backend/src/db/factories.ts +++ b/backend/src/db/factories.ts @@ -39,7 +39,7 @@ export const cleanDatabase = async ({ withMigrations } = { withMigrations: false return transaction.run(clean) }) } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/migrate/store.ts b/backend/src/db/migrate/store.ts index 947364e4d..bc70fe361 100644 --- a/backend/src/db/migrate/store.ts +++ b/backend/src/db/migrate/store.ts @@ -11,7 +11,7 @@ import { getDriver, getNeode } from '@db/neo4j' class Store { async init(errFn) { const neode = getNeode() - const session = neode.driver.session() + const session = neode.session() const txFreshIndicesConstrains = session.writeTransaction(async (txc) => { // drop all indices and constraints await txc.run('CALL apoc.schema.assert({},{},true)') @@ -38,6 +38,9 @@ class Store { // we need to have all constraints and indexes defined here. They can not be properly migrated await txFreshIndicesConstrains + // You have to wait for the schema to install, else the constraints will not be present. + // This is a type error of the library + // eslint-disable-next-line @typescript-eslint/await-thenable await getNeode().schema.install() // eslint-disable-next-line no-console console.log('Successfully created database indices and constraints!') @@ -46,8 +49,8 @@ class Store { console.log(error) // eslint-disable-line no-console errFn(error) } finally { - session.close() - neode.driver.close() + await session.close() + neode.close() } } @@ -76,7 +79,7 @@ class Store { console.log(error) // eslint-disable-line no-console next(error) } finally { - session.close() + await session.close() } } @@ -112,7 +115,7 @@ class Store { console.log(error) // eslint-disable-line no-console next(error) } finally { - session.close() + await session.close() } } } diff --git a/backend/src/db/migrate/template.ts b/backend/src/db/migrate/template.ts index ce538f260..9306ec27c 100644 --- a/backend/src/db/migrate/template.ts +++ b/backend/src/db/migrate/template.ts @@ -27,7 +27,7 @@ export async function up(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } @@ -48,6 +48,6 @@ export async function down(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/migrations-examples/20200123150105-merge_duplicate_user_accounts.ts b/backend/src/db/migrations-examples/20200123150105-merge_duplicate_user_accounts.ts index d13eeecf9..7d4195131 100644 --- a/backend/src/db/migrations-examples/20200123150105-merge_duplicate_user_accounts.ts +++ b/backend/src/db/migrations-examples/20200123150105-merge_duplicate_user_accounts.ts @@ -25,7 +25,8 @@ export const description = ` ` export function up(next) { const driver = getDriver() - const rxSession = driver.rxSession() + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const rxSession = driver.rxSession() as any rxSession .beginTransaction() .pipe( diff --git a/backend/src/db/migrations-examples/20200123150110-merge_duplicate_location_nodes.ts b/backend/src/db/migrations-examples/20200123150110-merge_duplicate_location_nodes.ts index 249464257..1b180616b 100644 --- a/backend/src/db/migrations-examples/20200123150110-merge_duplicate_location_nodes.ts +++ b/backend/src/db/migrations-examples/20200123150110-merge_duplicate_location_nodes.ts @@ -19,7 +19,8 @@ export const description = ` ` export function up(next) { const driver = getDriver() - const rxSession = driver.rxSession() + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const rxSession = driver.rxSession() as any rxSession .beginTransaction() .pipe( diff --git a/backend/src/db/migrations-examples/20200127110135-create_muted_relationship_between_existing_blocked_relationships.ts b/backend/src/db/migrations-examples/20200127110135-create_muted_relationship_between_existing_blocked_relationships.ts index cfc00fcfe..a8b6f8179 100644 --- a/backend/src/db/migrations-examples/20200127110135-create_muted_relationship_between_existing_blocked_relationships.ts +++ b/backend/src/db/migrations-examples/20200127110135-create_muted_relationship_between_existing_blocked_relationships.ts @@ -37,20 +37,20 @@ export async function up(_next) { // eslint-disable-next-line no-console console.log('rolled back') } finally { - session.close() + await session.close() } } -export function down(next) { +export async function down(next) { const driver = getDriver() const session = driver.session() try { // Rollback your migration here. - next() + // next() // eslint-disable-next-line no-catch-all/no-catch-all } catch (err) { next(err) } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/migrations-examples/20200207080200-fulltext_index_for_tags.ts b/backend/src/db/migrations-examples/20200207080200-fulltext_index_for_tags.ts index f7bcb0810..3fb44c77a 100644 --- a/backend/src/db/migrations-examples/20200207080200-fulltext_index_for_tags.ts +++ b/backend/src/db/migrations-examples/20200207080200-fulltext_index_for_tags.ts @@ -39,7 +39,7 @@ export async function up(next) { throw new Error(error) } } finally { - session.close() + await session.close() } } @@ -66,6 +66,6 @@ export async function down(next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/migrations-examples/20200213230248-add_unique_index_to_image_url.ts b/backend/src/db/migrations-examples/20200213230248-add_unique_index_to_image_url.ts index a22a38127..0af7626a1 100644 --- a/backend/src/db/migrations-examples/20200213230248-add_unique_index_to_image_url.ts +++ b/backend/src/db/migrations-examples/20200213230248-add_unique_index_to_image_url.ts @@ -40,7 +40,7 @@ export async function up(next) { throw new Error(error) } } finally { - session.close() + await session.close() } } @@ -64,6 +64,6 @@ export async function down(next) { // eslint-disable-next-line no-console console.log('rolled back') } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/migrations-examples/20200312140328-bulk_upload_to_s3.ts b/backend/src/db/migrations-examples/20200312140328-bulk_upload_to_s3.ts index bdbd459b3..606986d68 100644 --- a/backend/src/db/migrations-examples/20200312140328-bulk_upload_to_s3.ts +++ b/backend/src/db/migrations-examples/20200312140328-bulk_upload_to_s3.ts @@ -92,7 +92,7 @@ export async function up(next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } @@ -114,6 +114,6 @@ export async function down(next) { // eslint-disable-next-line no-console console.log('rolled back') } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/migrations-examples/20200320200315-refactor_all_images_to_separate_type.ts b/backend/src/db/migrations-examples/20200320200315-refactor_all_images_to_separate_type.ts index 61be45099..3028b9837 100644 --- a/backend/src/db/migrations-examples/20200320200315-refactor_all_images_to_separate_type.ts +++ b/backend/src/db/migrations-examples/20200320200315-refactor_all_images_to_separate_type.ts @@ -66,7 +66,7 @@ export async function up() { console.log('Created image nodes from all user avatars and post images.') printSummaries(stats) } finally { - session.close() + await session.close() } } @@ -104,6 +104,6 @@ export async function down() { console.log('UNDO: Split images from users and posts.') printSummaries(stats) } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/migrations-examples/20200323140300-remove_deleted_users_obsolete_attributes.ts b/backend/src/db/migrations-examples/20200323140300-remove_deleted_users_obsolete_attributes.ts index 36b29f477..d0e0ab5e6 100644 --- a/backend/src/db/migrations-examples/20200323140300-remove_deleted_users_obsolete_attributes.ts +++ b/backend/src/db/migrations-examples/20200323140300-remove_deleted_users_obsolete_attributes.ts @@ -28,7 +28,7 @@ export async function up(next) { `) try { // Implement your migration here. - const users = await updateDeletedUserAttributes.records.map((record) => record.get('user')) + const users = updateDeletedUserAttributes.records.map((record) => record.get('user')) // eslint-disable-next-line no-console console.log(users) await transaction.commit() @@ -41,7 +41,7 @@ export async function up(next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/migrations-examples/20200323160336-remove_deleted_posts_obsolete_attributes.ts b/backend/src/db/migrations-examples/20200323160336-remove_deleted_posts_obsolete_attributes.ts index 1a3e97dba..0bda73770 100644 --- a/backend/src/db/migrations-examples/20200323160336-remove_deleted_posts_obsolete_attributes.ts +++ b/backend/src/db/migrations-examples/20200323160336-remove_deleted_posts_obsolete_attributes.ts @@ -30,7 +30,7 @@ export async function up(next) { `) try { // Implement your migration here. - const posts = await updateDeletedPostsAttributes.records.map((record) => record.get('post')) + const posts = updateDeletedPostsAttributes.records.map((record) => record.get('post')) // eslint-disable-next-line no-console console.log(posts) await transaction.commit() @@ -43,7 +43,7 @@ export async function up(next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/migrations-examples/20200326160326-remove_dangling_image_urls.ts b/backend/src/db/migrations-examples/20200326160326-remove_dangling_image_urls.ts index 6f2d26c37..740b1a85e 100644 --- a/backend/src/db/migrations-examples/20200326160326-remove_dangling_image_urls.ts +++ b/backend/src/db/migrations-examples/20200326160326-remove_dangling_image_urls.ts @@ -62,7 +62,7 @@ export async function up(next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/migrations/1614023644903-add-clickedCount-to-posts.ts b/backend/src/db/migrations/1614023644903-add-clickedCount-to-posts.ts index 9bb7ab996..e1b884de0 100644 --- a/backend/src/db/migrations/1614023644903-add-clickedCount-to-posts.ts +++ b/backend/src/db/migrations/1614023644903-add-clickedCount-to-posts.ts @@ -27,7 +27,7 @@ export async function up(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } @@ -50,6 +50,6 @@ export async function down(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/migrations/1614177130817-add-viewedTeaserCount-to-posts.ts b/backend/src/db/migrations/1614177130817-add-viewedTeaserCount-to-posts.ts index 9ebfee85c..5ed831b61 100644 --- a/backend/src/db/migrations/1614177130817-add-viewedTeaserCount-to-posts.ts +++ b/backend/src/db/migrations/1614177130817-add-viewedTeaserCount-to-posts.ts @@ -27,7 +27,7 @@ export async function up(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } @@ -50,6 +50,6 @@ export async function down(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/migrations/20210506150512-add-donations-node.ts b/backend/src/db/migrations/20210506150512-add-donations-node.ts index 78919d46e..2cba177b7 100644 --- a/backend/src/db/migrations/20210506150512-add-donations-node.ts +++ b/backend/src/db/migrations/20210506150512-add-donations-node.ts @@ -43,7 +43,7 @@ export async function up(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } @@ -68,6 +68,6 @@ export async function down(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.ts b/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.ts index e4ed26095..c7c774c70 100644 --- a/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.ts +++ b/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.ts @@ -33,7 +33,7 @@ export async function up(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } @@ -60,6 +60,6 @@ export async function down(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/migrations/20220803060819-create_fulltext_indices_and_unique_keys_for_groups.ts b/backend/src/db/migrations/20220803060819-create_fulltext_indices_and_unique_keys_for_groups.ts index 9fa0ffcd2..106aec0dc 100644 --- a/backend/src/db/migrations/20220803060819-create_fulltext_indices_and_unique_keys_for_groups.ts +++ b/backend/src/db/migrations/20220803060819-create_fulltext_indices_and_unique_keys_for_groups.ts @@ -42,7 +42,7 @@ export async function up(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } @@ -74,6 +74,6 @@ export async function down(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/migrations/20230320130345-fulltext-search-indexes.ts b/backend/src/db/migrations/20230320130345-fulltext-search-indexes.ts index bf3541e7c..b12db9964 100644 --- a/backend/src/db/migrations/20230320130345-fulltext-search-indexes.ts +++ b/backend/src/db/migrations/20230320130345-fulltext-search-indexes.ts @@ -50,7 +50,7 @@ export async function up(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } @@ -75,6 +75,6 @@ export async function down(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/migrations/20230329150329-article-label-for-posts.ts b/backend/src/db/migrations/20230329150329-article-label-for-posts.ts index 1c1259ca0..06f05dbd1 100644 --- a/backend/src/db/migrations/20230329150329-article-label-for-posts.ts +++ b/backend/src/db/migrations/20230329150329-article-label-for-posts.ts @@ -30,7 +30,7 @@ export async function up(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } @@ -54,6 +54,6 @@ export async function down(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/migrations/20230608130637-add-postType-property.ts b/backend/src/db/migrations/20230608130637-add-postType-property.ts index e4f1033b5..f58e4de97 100644 --- a/backend/src/db/migrations/20230608130637-add-postType-property.ts +++ b/backend/src/db/migrations/20230608130637-add-postType-property.ts @@ -30,7 +30,7 @@ export async function up(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } @@ -54,6 +54,6 @@ export async function down(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/migrations/20231017141022-fix-event-dates.ts b/backend/src/db/migrations/20231017141022-fix-event-dates.ts index b3d7d14bd..1b92537ba 100644 --- a/backend/src/db/migrations/20231017141022-fix-event-dates.ts +++ b/backend/src/db/migrations/20231017141022-fix-event-dates.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-base-to-string */ /* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ @@ -26,11 +27,11 @@ export async function up(_next) { `) for (const event of events.records) { let [id, eventStart, eventEnd] = event - let date = new Date(eventStart) + let date = new Date(eventStart as string) date.setHours(date.getHours() - 1) eventStart = date.toISOString() if (eventEnd) { - date = new Date(eventEnd) + date = new Date(eventEnd as string) date.setHours(date.getHours() - 1) eventEnd = date.toISOString() } @@ -50,7 +51,7 @@ export async function up(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } @@ -69,6 +70,6 @@ export async function down(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/migrations/20250331130323-author-observes-own-post.ts b/backend/src/db/migrations/20250331130323-author-observes-own-post.ts index e088e12c1..4411107e7 100644 --- a/backend/src/db/migrations/20250331130323-author-observes-own-post.ts +++ b/backend/src/db/migrations/20250331130323-author-observes-own-post.ts @@ -37,7 +37,7 @@ export async function up(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } @@ -62,6 +62,6 @@ export async function down(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/migrations/20250331140313-commenter-observes-post.ts b/backend/src/db/migrations/20250331140313-commenter-observes-post.ts index f3f358f20..664c64047 100644 --- a/backend/src/db/migrations/20250331140313-commenter-observes-post.ts +++ b/backend/src/db/migrations/20250331140313-commenter-observes-post.ts @@ -37,7 +37,7 @@ export async function up(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } @@ -63,6 +63,6 @@ export async function down(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/migrations/20250405030454-email-notification-settings.ts b/backend/src/db/migrations/20250405030454-email-notification-settings.ts index f17c88fa9..afe3a5ed3 100644 --- a/backend/src/db/migrations/20250405030454-email-notification-settings.ts +++ b/backend/src/db/migrations/20250405030454-email-notification-settings.ts @@ -38,7 +38,7 @@ export async function up(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } @@ -69,6 +69,6 @@ export async function down(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/migrations/20250414220436-delete-old-badges.ts b/backend/src/db/migrations/20250414220436-delete-old-badges.ts index 9c1d2b8e1..6a932543e 100644 --- a/backend/src/db/migrations/20250414220436-delete-old-badges.ts +++ b/backend/src/db/migrations/20250414220436-delete-old-badges.ts @@ -30,7 +30,7 @@ export async function up(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } @@ -52,6 +52,6 @@ export async function down(_next) { console.log('rolled back') throw new Error(error) } finally { - session.close() + await session.close() } } diff --git a/backend/src/db/neo4j.ts b/backend/src/db/neo4j.ts index b7c0eec56..62594ee2d 100644 --- a/backend/src/db/neo4j.ts +++ b/backend/src/db/neo4j.ts @@ -4,13 +4,13 @@ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable import/no-named-as-default-member */ -import neo4j from 'neo4j-driver' +import neo4j, { Driver } from 'neo4j-driver' import Neode from 'neode' import CONFIG from '@config/index' import models from '@models/index' -let driver +let driver: Driver const defaultOptions = { uri: CONFIG.NEO4J_URI, username: CONFIG.NEO4J_USERNAME, @@ -25,7 +25,7 @@ export function getDriver(options = {}) { return driver } -let neodeInstance +let neodeInstance: Neode export function getNeode(options = {}) { if (!neodeInstance) { const { uri, username, password } = { ...defaultOptions, ...options } diff --git a/backend/src/db/seed.ts b/backend/src/db/seed.ts index 08594d1b4..6e09df986 100644 --- a/backend/src/db/seed.ts +++ b/backend/src/db/seed.ts @@ -1585,7 +1585,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] /* eslint-disable-next-line no-console */ console.log('Seeded Data...') await driver.close() - await neode.close() + neode.close() process.exit(0) // eslint-disable-next-line no-catch-all/no-catch-all } catch (err) { diff --git a/backend/src/jwt/decode.spec.ts b/backend/src/jwt/decode.spec.ts index 34dd86d68..0cd52a5d5 100644 --- a/backend/src/jwt/decode.spec.ts +++ b/backend/src/jwt/decode.spec.ts @@ -3,6 +3,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import Factory, { cleanDatabase } from '@db/factories' import { getDriver, getNeode } from '@db/neo4j' +import User from '@models/User' import decode from './decode' import encode from './encode' @@ -16,7 +17,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) // TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543 @@ -86,26 +87,28 @@ describe('decode', () => { }) it('sets `lastActiveAt`', async () => { - let user = await neode.first('User', { id: 'u3' }) + let user = await neode.first('User', { id: 'u3' }, undefined) await expect(user.toJson()).resolves.not.toHaveProperty('lastActiveAt') await decode(driver, validAuthorizationHeader) - user = await neode.first('User', { id: 'u3' }) + user = await neode.first('User', { id: 'u3' }, undefined) await expect(user.toJson()).resolves.toMatchObject({ lastActiveAt: expect.any(String), }) }) it('updates `lastActiveAt` for every authenticated request', async () => { - let user = await neode.first('User', { id: 'u3' }) + let user = await neode.first('User', { id: 'u3' }, undefined) await user.update({ - updatedAt: new Date().toISOString(), - lastActiveAt: '2019-10-03T23:33:08.598Z', + // eslint-disable-next-line @typescript-eslint/no-explicit-any + updatedAt: new Date().toISOString() as any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + lastActiveAt: '2019-10-03T23:33:08.598Z' as any, }) await expect(user.toJson()).resolves.toMatchObject({ lastActiveAt: '2019-10-03T23:33:08.598Z', }) await decode(driver, validAuthorizationHeader) - user = await neode.first('User', { id: 'u3' }) + user = await neode.first('User', { id: 'u3' }, undefined) await expect(user.toJson()).resolves.toMatchObject({ // should be a different time by now ;) lastActiveAt: expect.not.stringContaining('2019-10-03T23:33'), diff --git a/backend/src/middleware/hashtags/hashtagsMiddleware.spec.ts b/backend/src/middleware/hashtags/hashtagsMiddleware.spec.ts index 4f0d57303..bc3b96594 100644 --- a/backend/src/middleware/hashtags/hashtagsMiddleware.spec.ts +++ b/backend/src/middleware/hashtags/hashtagsMiddleware.spec.ts @@ -55,22 +55,15 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) beforeEach(async () => { - hashtagingUser = await neode.create( - 'User', - { - id: 'you', - name: 'Al Capone', - slug: 'al-capone', - }, - { - password: '1234', - email: 'test@example.org', - }, - ) + hashtagingUser = await neode.create('User', { + id: 'you', + name: 'Al Capone', + slug: 'al-capone', + }) await neode.create('Category', { id: 'cat9', name: 'Democracy & Politics', diff --git a/backend/src/middleware/languages/languages.spec.ts b/backend/src/middleware/languages/languages.spec.ts index 11ebf3a41..50e3a028f 100644 --- a/backend/src/middleware/languages/languages.spec.ts +++ b/backend/src/middleware/languages/languages.spec.ts @@ -32,7 +32,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) const createPostMutation = gql` diff --git a/backend/src/middleware/notifications/notificationsMiddleware.emails.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.emails.spec.ts index 55edef940..79d95e43e 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.emails.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.emails.spec.ts @@ -120,7 +120,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) describe('emails sent for notifications', () => { diff --git a/backend/src/middleware/notifications/notificationsMiddleware.followed-users.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.followed-users.spec.ts index 18da6bff8..7d311ce95 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.followed-users.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.followed-users.spec.ts @@ -94,7 +94,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) describe('following users notifications', () => { diff --git a/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts index 1c7ca4c71..96c7e9d18 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.mentions-in-groups.spec.ts @@ -116,7 +116,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) describe('mentions in groups', () => { diff --git a/backend/src/middleware/notifications/notificationsMiddleware.observing-posts.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.observing-posts.spec.ts index 9f193eaeb..a0864fe07 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.observing-posts.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.observing-posts.spec.ts @@ -102,7 +102,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) describe('notifications for users that observe a post', () => { diff --git a/backend/src/middleware/notifications/notificationsMiddleware.online-status.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.online-status.spec.ts index 47842029c..3a47d376d 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.online-status.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.online-status.spec.ts @@ -62,7 +62,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) afterEach(async () => { diff --git a/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts index 6bde0aee2..25aef2e2b 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.posts-in-groups.spec.ts @@ -118,7 +118,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) describe('notify group members of new posts in group', () => { diff --git a/backend/src/middleware/notifications/notificationsMiddleware.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.spec.ts index 31e458e2a..ab0a6a5b2 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.spec.ts @@ -88,7 +88,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) beforeEach(async () => { diff --git a/backend/src/middleware/orderByMiddleware.spec.ts b/backend/src/middleware/orderByMiddleware.spec.ts index 1ae15b26a..e46ee1c1f 100644 --- a/backend/src/middleware/orderByMiddleware.spec.ts +++ b/backend/src/middleware/orderByMiddleware.spec.ts @@ -28,7 +28,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) beforeEach(async () => { diff --git a/backend/src/middleware/permissionsMiddleware.spec.ts b/backend/src/middleware/permissionsMiddleware.spec.ts index 834e9888a..ca45005fe 100644 --- a/backend/src/middleware/permissionsMiddleware.spec.ts +++ b/backend/src/middleware/permissionsMiddleware.spec.ts @@ -33,7 +33,7 @@ describe('authorization', () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) // TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543 diff --git a/backend/src/middleware/permissionsMiddleware.ts b/backend/src/middleware/permissionsMiddleware.ts index 20063de11..3897a61e9 100644 --- a/backend/src/middleware/permissionsMiddleware.ts +++ b/backend/src/middleware/permissionsMiddleware.ts @@ -8,6 +8,7 @@ import { rule, shield, deny, allow, or, and } from 'graphql-shield' import CONFIG from '@config/index' import { getNeode } from '@db/neo4j' +import SocialMedia from '@models/SocialMedia' import { validateInviteCode } from '@schema/resolvers/transactions/inviteCodes' const debug = !!CONFIG.DEBUG @@ -48,15 +49,16 @@ const isMySocialMedia = rule({ if (!user) { return false } - let socialMedia = await neode.find('SocialMedia', args.id) + const socialMedia = await neode.find('SocialMedia', args.id) // Did we find a social media node? if (!socialMedia) { return false } - socialMedia = await socialMedia.toJson() // whats this for? + const socialMediaJson = await socialMedia.toJson() // whats this for? // Is it my social media entry? - return socialMedia.ownedBy.node.id === user.id + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (socialMediaJson.ownedBy as any).node.id === user.id }) const isAllowedToChangeGroupSettings = rule({ diff --git a/backend/src/middleware/slugifyMiddleware.spec.ts b/backend/src/middleware/slugifyMiddleware.spec.ts index 35247471c..75a52e4cf 100644 --- a/backend/src/middleware/slugifyMiddleware.spec.ts +++ b/backend/src/middleware/slugifyMiddleware.spec.ts @@ -42,7 +42,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) beforeEach(async () => { diff --git a/backend/src/middleware/softDelete/softDeleteMiddleware.spec.ts b/backend/src/middleware/softDelete/softDeleteMiddleware.spec.ts index 0264dedb9..ed9dcbf37 100644 --- a/backend/src/middleware/softDelete/softDeleteMiddleware.spec.ts +++ b/backend/src/middleware/softDelete/softDeleteMiddleware.spec.ts @@ -202,7 +202,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) describe('softDeleteMiddleware', () => { diff --git a/backend/src/middleware/userInteractions.spec.ts b/backend/src/middleware/userInteractions.spec.ts index fc096c2b7..61d92ff83 100644 --- a/backend/src/middleware/userInteractions.spec.ts +++ b/backend/src/middleware/userInteractions.spec.ts @@ -46,7 +46,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) describe('middleware/userInteractions', () => { diff --git a/backend/src/middleware/validation/validationMiddleware.spec.ts b/backend/src/middleware/validation/validationMiddleware.spec.ts index 3d3cd9bda..ea4f6ec54 100644 --- a/backend/src/middleware/validation/validationMiddleware.spec.ts +++ b/backend/src/middleware/validation/validationMiddleware.spec.ts @@ -79,7 +79,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) beforeEach(async () => { diff --git a/backend/src/models/User.spec.ts b/backend/src/models/User.spec.ts index 7d2c584b5..272ad2df3 100644 --- a/backend/src/models/User.spec.ts +++ b/backend/src/models/User.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ @@ -15,7 +16,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) // TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543 diff --git a/backend/src/schema/resolvers/badges.spec.ts b/backend/src/schema/resolvers/badges.spec.ts index 2588c9b5c..ae2fe0b0d 100644 --- a/backend/src/schema/resolvers/badges.spec.ts +++ b/backend/src/schema/resolvers/badges.spec.ts @@ -33,7 +33,7 @@ describe('Badges', () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) beforeEach(async () => { diff --git a/backend/src/schema/resolvers/comments.spec.ts b/backend/src/schema/resolvers/comments.spec.ts index 432723bae..a7177d754 100644 --- a/backend/src/schema/resolvers/comments.spec.ts +++ b/backend/src/schema/resolvers/comments.spec.ts @@ -30,7 +30,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) beforeEach(async () => { diff --git a/backend/src/schema/resolvers/donations.spec.ts b/backend/src/schema/resolvers/donations.spec.ts index 8e58153c1..8fc23d4e9 100644 --- a/backend/src/schema/resolvers/donations.spec.ts +++ b/backend/src/schema/resolvers/donations.spec.ts @@ -42,7 +42,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) describe('donations', () => { diff --git a/backend/src/schema/resolvers/emails.spec.ts b/backend/src/schema/resolvers/emails.spec.ts index d16adbf5d..f77602463 100644 --- a/backend/src/schema/resolvers/emails.spec.ts +++ b/backend/src/schema/resolvers/emails.spec.ts @@ -36,7 +36,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) beforeEach(async () => { @@ -110,11 +110,14 @@ describe('AddEmailAddress', () => { it('connects `UnverifiedEmailAddress` to the authenticated user', async () => { await mutate({ mutation, variables }) - const result = await neode.cypher(` + const result = await neode.cypher( + ` MATCH(u:User)-[:PRIMARY_EMAIL]->(:EmailAddress {email: "user@example.org"}) MATCH(u:User)<-[:BELONGS_TO]-(e:UnverifiedEmailAddress {email: "new-email@example.org"}) RETURN e - `) + `, + {}, + ) const email = neode.hydrateFirst(result, 'e', neode.model('UnverifiedEmailAddress')) await expect(email.toJson()).resolves.toMatchObject({ email: 'new-email@example.org', @@ -257,10 +260,13 @@ describe('VerifyEmailAddress', () => { it('connects the new `EmailAddress` as PRIMARY', async () => { await mutate({ mutation, variables }) - const result = await neode.cypher(` + const result = await neode.cypher( + ` MATCH(u:User {id: "567"})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: "to-be-verified@example.org"}) RETURN e - `) + `, + {}, + ) const email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress')) await expect(email.toJson()).resolves.toMatchObject({ email: 'to-be-verified@example.org', @@ -272,13 +278,13 @@ describe('VerifyEmailAddress', () => { MATCH(u:User {id: "567"})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: "user@example.org"}) RETURN e ` - let result = await neode.cypher(cypherStatement) + let result = await neode.cypher(cypherStatement, {}) let email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress')) await expect(email.toJson()).resolves.toMatchObject({ email: 'user@example.org', }) await mutate({ mutation, variables }) - result = await neode.cypher(cypherStatement) + result = await neode.cypher(cypherStatement, {}) email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress')) await expect(email).toBe(false) }) @@ -288,13 +294,13 @@ describe('VerifyEmailAddress', () => { MATCH(u:User {id: "567"})<-[:BELONGS_TO]-(e:EmailAddress {email: "user@example.org"}) RETURN e ` - let result = await neode.cypher(cypherStatement) + let result = await neode.cypher(cypherStatement, {}) let email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress')) await expect(email.toJson()).resolves.toMatchObject({ email: 'user@example.org', }) await mutate({ mutation, variables }) - result = await neode.cypher(cypherStatement) + result = await neode.cypher(cypherStatement, {}) email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress')) await expect(email).toBe(false) }) @@ -319,10 +325,13 @@ describe('VerifyEmailAddress', () => { it('connects the new `EmailAddress` as PRIMARY', async () => { await mutate({ mutation, variables }) - const result = await neode.cypher(` + const result = await neode.cypher( + ` MATCH(u:User {id: "567"})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: "to-be-verified@example.org"}) RETURN e - `) + `, + {}, + ) const email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress')) await expect(email.toJson()).resolves.toMatchObject({ email: 'to-be-verified@example.org', diff --git a/backend/src/schema/resolvers/filter-posts.spec.ts b/backend/src/schema/resolvers/filter-posts.spec.ts index 87ba2a8e5..c29b98365 100644 --- a/backend/src/schema/resolvers/filter-posts.spec.ts +++ b/backend/src/schema/resolvers/filter-posts.spec.ts @@ -38,7 +38,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) describe('Filter Posts', () => { diff --git a/backend/src/schema/resolvers/follow.spec.ts b/backend/src/schema/resolvers/follow.spec.ts index 437b4e160..e846eb56f 100644 --- a/backend/src/schema/resolvers/follow.spec.ts +++ b/backend/src/schema/resolvers/follow.spec.ts @@ -76,7 +76,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) beforeEach(async () => { diff --git a/backend/src/schema/resolvers/follow.ts b/backend/src/schema/resolvers/follow.ts index d3c1a9081..809d77760 100644 --- a/backend/src/schema/resolvers/follow.ts +++ b/backend/src/schema/resolvers/follow.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-call */ diff --git a/backend/src/schema/resolvers/groups.spec.ts b/backend/src/schema/resolvers/groups.spec.ts index 39ab87dd4..664f57397 100644 --- a/backend/src/schema/resolvers/groups.spec.ts +++ b/backend/src/schema/resolvers/groups.spec.ts @@ -241,7 +241,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) describe('in mode', () => { diff --git a/backend/src/schema/resolvers/images/images.spec.ts b/backend/src/schema/resolvers/images/images.spec.ts index 0a2cbadbd..938571126 100644 --- a/backend/src/schema/resolvers/images/images.spec.ts +++ b/backend/src/schema/resolvers/images/images.spec.ts @@ -25,7 +25,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) beforeEach(async () => { @@ -83,7 +83,7 @@ describe('deleteImage', () => { return result }) } finally { - session.close() + await session.close() } await expect(neode.all('Image')).resolves.toHaveLength(0) await expect(someString).toEqual('Hello') @@ -106,7 +106,7 @@ describe('deleteImage', () => { await expect(neode.all('Image')).resolves.toHaveLength(1) // all good } finally { - session.close() + await session.close() } }) }) @@ -198,9 +198,10 @@ describe('mergeImage', () => { it('connects resource with image via given image type', async () => { await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback }) - const result = await neode.cypher(` - MATCH(p:Post {id: "p99"})-[:HERO_IMAGE]->(i:Image) RETURN i,p - `) + const result = await neode.cypher( + `MATCH(p:Post {id: "p99"})-[:HERO_IMAGE]->(i:Image) RETURN i,p`, + {}, + ) post = neode.hydrateFirst(result, 'p', neode.model('Post')) const image = neode.hydrateFirst(result, 'i', neode.model('Image')) expect(post).toBeTruthy() @@ -215,7 +216,7 @@ describe('mergeImage', () => { it('sets metadata', async () => { await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback }) - const image = await neode.first('Image', {}) + const image = await neode.first('Image', {}, undefined) await expect(image.toJson()).resolves.toMatchObject({ alt: 'A description of the new image', createdAt: expect.any(String), @@ -243,9 +244,13 @@ describe('mergeImage', () => { ) }) } finally { - session.close() + await session.close() } - const image = await neode.first('Image', { alt: 'This alt text gets overwritten' }) + const image = await neode.first( + 'Image', + { alt: 'This alt text gets overwritten' }, + undefined, + ) await expect(image.toJson()).resolves.toMatchObject({ alt: 'This alt text gets overwritten', }) @@ -268,7 +273,7 @@ describe('mergeImage', () => { await expect(neode.all('Image')).resolves.toHaveLength(0) // all good } finally { - session.close() + await session.close() } }) }) @@ -296,7 +301,7 @@ describe('mergeImage', () => { await expect(neode.all('Image')).resolves.toHaveLength(1) await mergeImage(post, 'HERO_IMAGE', imageInput, { uploadCallback, deleteCallback }) await expect(neode.all('Image')).resolves.toHaveLength(1) - const image = await neode.first('Image', {}) + const image = await neode.first('Image', {}, undefined) await expect(image.toJson()).resolves.toMatchObject({ alt: 'A description of the new image', createdAt: expect.any(String), diff --git a/backend/src/schema/resolvers/images/images.ts b/backend/src/schema/resolvers/images/images.ts index 11532347d..2e76a7889 100644 --- a/backend/src/schema/resolvers/images/images.ts +++ b/backend/src/schema/resolvers/images/images.ts @@ -91,7 +91,7 @@ const wrapTransaction = async (wrappedCallback, args, opts) => { }) return result } finally { - session.close() + await session.close() } } diff --git a/backend/src/schema/resolvers/inviteCodes.spec.ts b/backend/src/schema/resolvers/inviteCodes.spec.ts index aac79210f..7d335077a 100644 --- a/backend/src/schema/resolvers/inviteCodes.spec.ts +++ b/backend/src/schema/resolvers/inviteCodes.spec.ts @@ -57,7 +57,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) describe('inviteCodes', () => { diff --git a/backend/src/schema/resolvers/locations.spec.ts b/backend/src/schema/resolvers/locations.spec.ts index d4510284c..aed85da54 100644 --- a/backend/src/schema/resolvers/locations.spec.ts +++ b/backend/src/schema/resolvers/locations.spec.ts @@ -30,7 +30,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) // TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543 diff --git a/backend/src/schema/resolvers/messages.spec.ts b/backend/src/schema/resolvers/messages.spec.ts index 7b46e0205..8061cf460 100644 --- a/backend/src/schema/resolvers/messages.spec.ts +++ b/backend/src/schema/resolvers/messages.spec.ts @@ -45,7 +45,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) describe('Message', () => { diff --git a/backend/src/schema/resolvers/moderation.spec.ts b/backend/src/schema/resolvers/moderation.spec.ts index 67d070893..f3224421e 100644 --- a/backend/src/schema/resolvers/moderation.spec.ts +++ b/backend/src/schema/resolvers/moderation.spec.ts @@ -75,7 +75,7 @@ describe('moderate resources', () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) beforeEach(async () => { @@ -194,7 +194,7 @@ describe('moderate resources', () => { ]) const cypher = 'MATCH (:Report)<-[review:REVIEWED]-(moderator:User {id: "moderator-id"}) RETURN review' - const reviews = await neode.cypher(cypher) + const reviews = await neode.cypher(cypher, {}) expect(reviews.records).toHaveLength(1) }) diff --git a/backend/src/schema/resolvers/notifications.spec.ts b/backend/src/schema/resolvers/notifications.spec.ts index 2aebe4c24..d6d22e459 100644 --- a/backend/src/schema/resolvers/notifications.spec.ts +++ b/backend/src/schema/resolvers/notifications.spec.ts @@ -38,7 +38,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) beforeEach(async () => { diff --git a/backend/src/schema/resolvers/observePosts.spec.ts b/backend/src/schema/resolvers/observePosts.spec.ts index 9176d424e..76ad5b058 100644 --- a/backend/src/schema/resolvers/observePosts.spec.ts +++ b/backend/src/schema/resolvers/observePosts.spec.ts @@ -61,7 +61,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) describe('observing posts', () => { diff --git a/backend/src/schema/resolvers/passwordReset.spec.ts b/backend/src/schema/resolvers/passwordReset.spec.ts index 7a260d345..66823cc5d 100644 --- a/backend/src/schema/resolvers/passwordReset.spec.ts +++ b/backend/src/schema/resolvers/passwordReset.spec.ts @@ -22,6 +22,7 @@ let variables const getAllPasswordResets = async () => { const passwordResetQuery = await neode.cypher( 'MATCH (passwordReset:PasswordReset) RETURN passwordReset', + {}, ) const resets = passwordResetQuery.records.map((record) => record.get('passwordReset')) return resets @@ -44,7 +45,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) beforeEach(() => { diff --git a/backend/src/schema/resolvers/posts.spec.ts b/backend/src/schema/resolvers/posts.spec.ts index 403db3950..0a05200fd 100644 --- a/backend/src/schema/resolvers/posts.spec.ts +++ b/backend/src/schema/resolvers/posts.spec.ts @@ -10,6 +10,7 @@ import CONFIG from '@config/index' import Factory, { cleanDatabase } from '@db/factories' import { getNeode, getDriver } from '@db/neo4j' import { createPostMutation } from '@graphql/queries/createPostMutation' +import Image from '@models/Image' import createServer from '@src/server' CONFIG.CATEGORIES_ACTIVE = true @@ -46,7 +47,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) beforeEach(async () => { @@ -974,9 +975,13 @@ describe('UpdatePost', () => { variables = { ...variables, image: { sensitive: true } } }) it('updates the image', async () => { - await expect(neode.first('Image', { sensitive: true })).resolves.toBeFalsy() + await expect( + neode.first('Image', { sensitive: true }, undefined), + ).resolves.toBeFalsy() await mutate({ mutation: updatePostMutation, variables }) - await expect(neode.first('Image', { sensitive: true })).resolves.toBeTruthy() + await expect( + neode.first('Image', { sensitive: true }, undefined), + ).resolves.toBeTruthy() }) }) @@ -996,9 +1001,13 @@ describe('UpdatePost', () => { delete variables.image }) it('keeps the image unchanged', async () => { - await expect(neode.first('Image', { sensitive: true })).resolves.toBeFalsy() + await expect( + neode.first('Image', { sensitive: true }, undefined), + ).resolves.toBeFalsy() await mutate({ mutation: updatePostMutation, variables }) - await expect(neode.first('Image', { sensitive: true })).resolves.toBeFalsy() + await expect( + neode.first('Image', { sensitive: true }, undefined), + ).resolves.toBeFalsy() }) }) }) @@ -1244,11 +1253,11 @@ describe('pin posts', () => { it('removes previous `pinned` attribute', async () => { const cypher = 'MATCH (post:Post) WHERE post.pinned IS NOT NULL RETURN post' - pinnedPost = await neode.cypher(cypher) + pinnedPost = await neode.cypher(cypher, {}) expect(pinnedPost.records).toHaveLength(1) variables = { ...variables, id: 'only-pinned-post' } await mutate({ mutation: pinPostMutation, variables }) - pinnedPost = await neode.cypher(cypher) + pinnedPost = await neode.cypher(cypher, {}) expect(pinnedPost.records).toHaveLength(1) }) @@ -1257,6 +1266,7 @@ describe('pin posts', () => { await mutate({ mutation: pinPostMutation, variables }) pinnedPost = await neode.cypher( `MATCH (:User)-[pinned:PINNED]->(post:Post) RETURN post, pinned`, + {}, ) expect(pinnedPost.records).toHaveLength(1) }) diff --git a/backend/src/schema/resolvers/postsInGroups.spec.ts b/backend/src/schema/resolvers/postsInGroups.spec.ts index 664a64b9f..7cb0bdc76 100644 --- a/backend/src/schema/resolvers/postsInGroups.spec.ts +++ b/backend/src/schema/resolvers/postsInGroups.spec.ts @@ -63,7 +63,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) describe('Posts in Groups', () => { diff --git a/backend/src/schema/resolvers/registration.spec.ts b/backend/src/schema/resolvers/registration.spec.ts index 9a0e578cd..deaee0d08 100644 --- a/backend/src/schema/resolvers/registration.spec.ts +++ b/backend/src/schema/resolvers/registration.spec.ts @@ -9,6 +9,8 @@ import gql from 'graphql-tag' import CONFIG from '@config/index' import Factory, { cleanDatabase } from '@db/factories' import { getDriver, getNeode } from '@db/neo4j' +import EmailAddress from '@models/EmailAddress' +import User from '@models/User' import createServer from '@src/server' const neode = getNeode() @@ -35,7 +37,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) beforeEach(async () => { @@ -97,17 +99,27 @@ describe('Signup', () => { describe('creates a EmailAddress node', () => { it('with `createdAt` attribute', async () => { await mutate({ mutation, variables }) - let emailAddress = await neode.first('EmailAddress', { email: 'someuser@example.org' }) - emailAddress = await emailAddress.toJson() - expect(emailAddress.createdAt).toBeTruthy() - expect(Date.parse(emailAddress.createdAt)).toEqual(expect.any(Number)) + const emailAddress = await neode.first( + 'EmailAddress', + { email: 'someuser@example.org' }, + undefined, + ) + const emailAddressJson = await emailAddress.toJson() + expect(emailAddressJson.createdAt).toBeTruthy() + expect(Date.parse(emailAddressJson.createdAt as unknown as string)).toEqual( + expect.any(Number), + ) }) it('with a cryptographic `nonce`', async () => { await mutate({ mutation, variables }) - let emailAddress = await neode.first('EmailAddress', { email: 'someuser@example.org' }) - emailAddress = await emailAddress.toJson() - expect(emailAddress.nonce).toEqual(expect.any(String)) + const emailAddress = await neode.first( + 'EmailAddress', + { email: 'someuser@example.org' }, + undefined, + ) + const emailAddressJson = await emailAddress.toJson() + expect(emailAddressJson.nonce).toEqual(expect.any(String)) }) describe('if the email already exists', () => { @@ -247,7 +259,11 @@ describe('SignupVerification', () => { it('sets `verifiedAt` attribute of EmailAddress', async () => { await mutate({ mutation, variables }) - const email = await neode.first('EmailAddress', { email: 'john@example.org' }) + const email = await neode.first( + 'EmailAddress', + { email: 'john@example.org' }, + undefined, + ) await expect(email.toJson()).resolves.toEqual( expect.objectContaining({ verifiedAt: expect.any(String), @@ -268,7 +284,7 @@ describe('SignupVerification', () => { it('sets `about` attribute of User', async () => { variables = { ...variables, about: 'Find this description in the user profile' } await mutate({ mutation, variables }) - const user = await neode.first('User', { name: 'John Doe' }) + const user = await neode.first('User', { name: 'John Doe' }, undefined) await expect(user.toJson()).resolves.toMatchObject({ about: 'Find this description in the user profile', }) diff --git a/backend/src/schema/resolvers/reports.spec.ts b/backend/src/schema/resolvers/reports.spec.ts index 4401329cb..bcbe1df4e 100644 --- a/backend/src/schema/resolvers/reports.spec.ts +++ b/backend/src/schema/resolvers/reports.spec.ts @@ -122,7 +122,7 @@ describe('file a report on a resource', () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) // TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543 diff --git a/backend/src/schema/resolvers/rooms.spec.ts b/backend/src/schema/resolvers/rooms.spec.ts index f374285e1..9a226a2f8 100644 --- a/backend/src/schema/resolvers/rooms.spec.ts +++ b/backend/src/schema/resolvers/rooms.spec.ts @@ -40,7 +40,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) describe('Room', () => { diff --git a/backend/src/schema/resolvers/searches.spec.ts b/backend/src/schema/resolvers/searches.spec.ts index cb774cad5..8a94fbf21 100644 --- a/backend/src/schema/resolvers/searches.spec.ts +++ b/backend/src/schema/resolvers/searches.spec.ts @@ -30,7 +30,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() neode.close() }) diff --git a/backend/src/schema/resolvers/shout.spec.ts b/backend/src/schema/resolvers/shout.spec.ts index 4ec6189bd..9023284c6 100644 --- a/backend/src/schema/resolvers/shout.spec.ts +++ b/backend/src/schema/resolvers/shout.spec.ts @@ -56,7 +56,7 @@ describe('shout and unshout posts', () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) beforeEach(async () => { diff --git a/backend/src/schema/resolvers/socialMedia.spec.ts b/backend/src/schema/resolvers/socialMedia.spec.ts index 584e64cfb..168360a3b 100644 --- a/backend/src/schema/resolvers/socialMedia.spec.ts +++ b/backend/src/schema/resolvers/socialMedia.spec.ts @@ -18,7 +18,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) describe('SocialMedia', () => { diff --git a/backend/src/schema/resolvers/socialMedia.ts b/backend/src/schema/resolvers/socialMedia.ts index d3a563d2c..ad2bdabe5 100644 --- a/backend/src/schema/resolvers/socialMedia.ts +++ b/backend/src/schema/resolvers/socialMedia.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-call */ diff --git a/backend/src/schema/resolvers/statistics.spec.ts b/backend/src/schema/resolvers/statistics.spec.ts index 9d68b611f..50f124ac9 100644 --- a/backend/src/schema/resolvers/statistics.spec.ts +++ b/backend/src/schema/resolvers/statistics.spec.ts @@ -44,7 +44,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) // TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543 diff --git a/backend/src/schema/resolvers/userData.spec.ts b/backend/src/schema/resolvers/userData.spec.ts index b3cd75694..17f1f4446 100644 --- a/backend/src/schema/resolvers/userData.spec.ts +++ b/backend/src/schema/resolvers/userData.spec.ts @@ -64,7 +64,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) describe('resolvers/userData', () => { diff --git a/backend/src/schema/resolvers/user_management.spec.ts b/backend/src/schema/resolvers/user_management.spec.ts index 92832baa2..1029ab2b1 100644 --- a/backend/src/schema/resolvers/user_management.spec.ts +++ b/backend/src/schema/resolvers/user_management.spec.ts @@ -4,6 +4,8 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable promise/prefer-await-to-callbacks */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable jest/unbound-method */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' import jwt from 'jsonwebtoken' @@ -56,7 +58,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) beforeEach(() => { @@ -160,8 +162,16 @@ describe('currentUser', () => { await respondsWith({ data: { currentUser: expect.objectContaining({ - activeCategories: [ + activeCategories: expect.arrayContaining([ 'cat1', + 'cat2', + 'cat3', + 'cat4', + 'cat5', + 'cat6', + 'cat7', + 'cat8', + 'cat9', 'cat10', 'cat11', 'cat12', @@ -172,15 +182,7 @@ describe('currentUser', () => { 'cat17', 'cat18', 'cat19', - 'cat2', - 'cat3', - 'cat4', - 'cat5', - 'cat6', - 'cat7', - 'cat8', - 'cat9', - ], + ]), }), }, }) @@ -272,7 +274,11 @@ describe('login', () => { describe('normalization', () => { describe('email address is a gmail address ', () => { beforeEach(async () => { - const email = await neode.first('EmailAddress', { email: 'test@example.org' }) + const email = await neode.first( + 'EmailAddress', + { email: 'test@example.org' }, + undefined, + ) await email.update({ email: 'someuser@gmail.com' }) }) diff --git a/backend/src/schema/resolvers/user_management.ts b/backend/src/schema/resolvers/user_management.ts index 072755850..6b84f7256 100644 --- a/backend/src/schema/resolvers/user_management.ts +++ b/backend/src/schema/resolvers/user_management.ts @@ -61,7 +61,7 @@ export default { changePassword: async (_, { oldPassword, newPassword }, { user }) => { const currentUser = await neode.find('User', user.id) - const encryptedPassword = currentUser.get('encryptedPassword') + const encryptedPassword = currentUser.get('encryptedPassword') if (!(await bcrypt.compare(oldPassword, encryptedPassword))) { throw new AuthenticationError('Old password is not correct') } diff --git a/backend/src/schema/resolvers/users.spec.ts b/backend/src/schema/resolvers/users.spec.ts index d4f5e00eb..ad37e2024 100644 --- a/backend/src/schema/resolvers/users.spec.ts +++ b/backend/src/schema/resolvers/users.spec.ts @@ -9,6 +9,7 @@ import gql from 'graphql-tag' import { categories } from '@constants/categories' import Factory, { cleanDatabase } from '@db/factories' import { getNeode, getDriver } from '@db/neo4j' +import User from '@models/User' import createServer from '@src/server' const categoryIds = ['cat9'] @@ -125,7 +126,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) // TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543 @@ -1083,7 +1084,7 @@ describe('updateOnlineStatus', () => { const cypher = 'MATCH (u:User {id: $id}) RETURN u' const result = await neode.cypher(cypher, { id: authenticatedUser.id }) - const dbUser = neode.hydrateFirst(result, 'u', neode.model('User')) + const dbUser = neode.hydrateFirst(result, 'u', neode.model('User')) await expect(dbUser.toJson()).resolves.toMatchObject({ lastOnlineStatus: 'away', awaySince: expect.any(String), @@ -1587,14 +1588,14 @@ describe('resetTrophyBadgesSelected', () => { isDefault: true, }, ], - badgeTrophiesUnused: [ + badgeTrophiesUnused: expect.arrayContaining([ { id: 'trophy_panda', }, { id: 'trophy_bear', }, - ], + ]), badgeTrophiesUnusedCount: 2, }, }, diff --git a/backend/src/schema/resolvers/users/location.spec.ts b/backend/src/schema/resolvers/users/location.spec.ts index 9c3791e35..659c126dd 100644 --- a/backend/src/schema/resolvers/users/location.spec.ts +++ b/backend/src/schema/resolvers/users/location.spec.ts @@ -94,7 +94,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) beforeEach(() => { @@ -213,6 +213,7 @@ describe('userMiddleware', () => { await mutate({ mutation: updateUserMutation, variables }) const locations = await neode.cypher( `MATCH (city:Location)-[:IS_IN]->(district:Location)-[:IS_IN]->(state:Location)-[:IS_IN]->(country:Location) return city {.*}, state {.*}, country {.*}`, + {}, ) expect( locations.records.map((record) => { diff --git a/backend/src/schema/resolvers/users/mutedUsers.spec.ts b/backend/src/schema/resolvers/users/mutedUsers.spec.ts index 455672199..ccb6d2a87 100644 --- a/backend/src/schema/resolvers/users/mutedUsers.spec.ts +++ b/backend/src/schema/resolvers/users/mutedUsers.spec.ts @@ -23,7 +23,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) beforeEach(() => { diff --git a/backend/src/schema/resolvers/viewedTeaserCount.spec.ts b/backend/src/schema/resolvers/viewedTeaserCount.spec.ts index 9fb8a7eb9..f4fba31f8 100644 --- a/backend/src/schema/resolvers/viewedTeaserCount.spec.ts +++ b/backend/src/schema/resolvers/viewedTeaserCount.spec.ts @@ -32,7 +32,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDatabase() - driver.close() + await driver.close() }) describe('count post teaser views', () => { From 4e9dbf4c98ac94a2e06502dd345bdbcb67ffc64c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Fri, 25 Apr 2025 21:38:49 +0800 Subject: [PATCH 36/54] fix(docu): remove required but missing `frontend/.env` (#8431) When you follow the documentation, your `docker compose up` will fail because a `.env` file is referenced that doesn't exist yet. The documentation mentions a `.env.template` file, so I guess that one was deleted but the documentation not updated. --- README.md | 4 ---- docker-compose.yml | 2 -- 2 files changed, 6 deletions(-) diff --git a/README.md b/README.md index b1fe0ea14..910eae5a4 100644 --- a/README.md +++ b/README.md @@ -187,10 +187,6 @@ $ cp .env.template .env # in folder backend/ $ cp .env.template .env -# in folder frontend/ -$ cp .env.template .env -``` - For Development: ```bash diff --git a/docker-compose.yml b/docker-compose.yml index d46b5cd29..8397c4e47 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,8 +43,6 @@ services: # Application only envs #- HOST=0.0.0.0 # This is nuxt specific, alternative value is HOST=webapp #- GRAPHQL_URI=http://backend:4000 - env_file: - - ./frontend/.env backend: image: ghcr.io/ocelot-social-community/ocelot-social/backend:${OCELOT_VERSION:-latest} From 0873fc748c365f774ef08c85d8257208c4f36c14 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 25 Apr 2025 16:31:49 +0200 Subject: [PATCH 37/54] feat(backend): lint - detect unused typescript disables (#8425) * detect unused typescript disables * fix lint errors uuid-types remove debug fix config * lint fixes --- backend/package.json | 3 ++- backend/src/config/index.ts | 20 ++---------------- backend/src/db/admin.ts | 5 +---- backend/src/db/badges.ts | 3 --- backend/src/db/categories.ts | 4 +--- backend/src/db/compiler.ts | 2 +- backend/src/db/factories.ts | 2 -- backend/src/db/migrate/store.ts | 4 ---- backend/src/db/migrate/template.ts | 8 +------ ...123150105-merge_duplicate_user_accounts.ts | 2 -- ...23150110-merge_duplicate_location_nodes.ts | 2 -- ..._between_existing_blocked_relationships.ts | 8 +------ ...0206190233-swap_latitude_with_longitude.ts | 6 +----- .../20200207080200-fulltext_index_for_tags.ts | 4 ---- ...213230248-add_unique_index_to_image_url.ts | 4 ---- .../20200312140328-bulk_upload_to_s3.ts | 2 -- ...15-refactor_all_images_to_separate_type.ts | 7 +------ ...emove_deleted_users_obsolete_attributes.ts | 5 +---- ...emove_deleted_posts_obsolete_attributes.ts | 5 +---- ...200326160326-remove_dangling_image_urls.ts | 3 +-- ...1614023644903-add-clickedCount-to-posts.ts | 4 +--- ...77130817-add-viewedTeaserCount-to-posts.ts | 4 +--- .../20210506150512-add-donations-node.ts | 8 +------ ...otificationEmails-property-to-all-users.ts | 8 +------ ...text_indices_and_unique_keys_for_groups.ts | 8 +------ .../20230320130345-fulltext-search-indexes.ts | 8 +------ .../20230329150329-article-label-for-posts.ts | 8 +------ .../20230608130637-add-postType-property.ts | 8 +------ .../20231017141022-fix-event-dates.ts | 7 +------ ...20250331130323-author-observes-own-post.ts | 8 +------ .../20250331140313-commenter-observes-post.ts | 8 +------ ...50405030454-email-notification-settings.ts | 8 +------ .../20250414220436-delete-old-badges.ts | 8 +------ backend/src/db/neo4j.ts | 4 +--- backend/src/db/seed.ts | 2 -- ...ficationsMiddleware.followed-users.spec.ts | 2 +- .../notifications/notificationsMiddleware.ts | 1 - .../src/middleware/orderByMiddleware.spec.ts | 2 -- backend/src/models/Category.ts | 1 - backend/src/models/Comment.ts | 1 - backend/src/models/Donations.ts | 1 - backend/src/models/Group.ts | 1 - backend/src/models/Post.ts | 1 - backend/src/models/Report.ts | 1 - backend/src/models/SocialMedia.ts | 1 - backend/src/models/User.spec.ts | 5 +---- backend/src/models/User.ts | 1 - backend/src/schema/resolvers/follow.ts | 4 ++-- .../src/schema/resolvers/helpers/Resolver.ts | 5 ----- .../resolvers/helpers/databaseLogger.ts | 21 ------------------- backend/src/schema/resolvers/moderation.ts | 3 --- backend/src/schema/resolvers/notifications.ts | 5 ----- backend/src/schema/resolvers/passwordReset.ts | 2 +- .../src/schema/resolvers/registration.spec.ts | 1 - backend/src/schema/resolvers/reports.ts | 6 ------ backend/src/schema/resolvers/searches.ts | 2 -- backend/src/schema/resolvers/shout.ts | 4 ---- backend/src/schema/resolvers/socialMedia.ts | 4 ++-- backend/src/schema/resolvers/statistics.ts | 3 --- .../src/schema/resolvers/user_management.ts | 4 +--- backend/src/schema/resolvers/users.ts | 2 -- .../src/schema/resolvers/users/location.ts | 7 ------- backend/test/setup.ts | 8 ------- backend/yarn.lock | 5 +++++ 64 files changed, 41 insertions(+), 263 deletions(-) delete mode 100644 backend/src/schema/resolvers/helpers/databaseLogger.ts diff --git a/backend/package.json b/backend/package.json index 7137d5c25..d904bbd20 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,7 +12,7 @@ "build": "tsc && tsc-alias && ./scripts/build.copy.files.sh", "dev": "nodemon --exec ts-node --require tsconfig-paths/register src/ -e js,ts,gql", "dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/ -e js,ts,gql", - "lint": "eslint --max-warnings=0 --ext .js,.ts .", + "lint": "eslint --max-warnings=0 --report-unused-disable-directives --ext .js,.ts .", "test": "cross-env NODE_ENV=test NODE_OPTIONS=--max-old-space-size=8192 jest --runInBand --coverage --forceExit --detectOpenHandles", "db:reset": "ts-node --require tsconfig-paths/register src/db/reset.ts", "db:reset:withmigrations": "ts-node --require tsconfig-paths/register src/db/reset-with-migrations.ts", @@ -105,6 +105,7 @@ "@faker-js/faker": "9.7.0", "@types/jest": "^29.5.14", "@types/node": "^22.14.1", + "@types/uuid": "~9.0.1", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", "apollo-server-testing": "~2.11.0", diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index cf07297df..35e800c64 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -1,35 +1,19 @@ -/* eslint-disable @typescript-eslint/require-await */ -/* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ /* eslint-disable n/no-process-env */ -/* eslint-disable n/no-unpublished-require */ -/* eslint-disable n/no-missing-require */ import { config } from 'dotenv' import emails from './emails' import metadata from './metadata' // Load env file -if (require.resolve) { - try { - config({ path: require.resolve('../../.env') }) - } catch (error) { - // This error is thrown when the .env is not found - if (error.code !== 'MODULE_NOT_FOUND') { - throw error - } - } -} +config() // Use Cypress env or process.env // eslint-disable-next-line @typescript-eslint/no-explicit-any declare let Cypress: any | undefined -const env = typeof Cypress !== 'undefined' ? Cypress.env() : process.env // eslint-disable-line no-undef +const env = typeof Cypress !== 'undefined' ? Cypress.env() : process.env const environment = { NODE_ENV: env.NODE_ENV || process.env.NODE_ENV, diff --git a/backend/src/db/admin.ts b/backend/src/db/admin.ts index b60eeb38a..1f62c8733 100644 --- a/backend/src/db/admin.ts +++ b/backend/src/db/admin.ts @@ -1,9 +1,6 @@ /* eslint-disable @typescript-eslint/no-floating-promises */ /* eslint-disable @typescript-eslint/require-await */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ + import { hashSync } from 'bcryptjs' import { v4 as uuid } from 'uuid' diff --git a/backend/src/db/badges.ts b/backend/src/db/badges.ts index 3fbbd4d7f..cbad0b004 100644 --- a/backend/src/db/badges.ts +++ b/backend/src/db/badges.ts @@ -1,6 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-floating-promises */ import { getNeode } from './neo4j' import { trophies, verification } from './seed/badges' diff --git a/backend/src/db/categories.ts b/backend/src/db/categories.ts index b3774e7b9..a007b25ae 100644 --- a/backend/src/db/categories.ts +++ b/backend/src/db/categories.ts @@ -1,9 +1,7 @@ /* eslint-disable @typescript-eslint/no-floating-promises */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/require-await */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ + import { categories } from '@constants/categories' import { getDriver } from './neo4j' diff --git a/backend/src/db/compiler.ts b/backend/src/db/compiler.ts index 9c2140f2a..1b364f919 100644 --- a/backend/src/db/compiler.ts +++ b/backend/src/db/compiler.ts @@ -3,7 +3,7 @@ /* eslint-disable import/no-commonjs */ // eslint-disable-next-line n/no-unpublished-require, @typescript-eslint/no-var-requires const tsNode = require('ts-node') -// eslint-disable-next-line import/no-unassigned-import, import/no-extraneous-dependencies, n/no-unpublished-require +// eslint-disable-next-line import/no-unassigned-import, n/no-unpublished-require require('tsconfig-paths/register') module.exports = tsNode.register diff --git a/backend/src/db/factories.ts b/backend/src/db/factories.ts index e951c3839..90a666205 100644 --- a/backend/src/db/factories.ts +++ b/backend/src/db/factories.ts @@ -1,11 +1,9 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/unbound-method */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ import { faker } from '@faker-js/faker' import { hashSync } from 'bcryptjs' import { Factory } from 'rosie' diff --git a/backend/src/db/migrate/store.ts b/backend/src/db/migrate/store.ts index bc70fe361..9976be8b4 100644 --- a/backend/src/db/migrate/store.ts +++ b/backend/src/db/migrate/store.ts @@ -1,11 +1,7 @@ -/* eslint-disable @typescript-eslint/require-await */ -/* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ import { getDriver, getNeode } from '@db/neo4j' class Store { diff --git a/backend/src/db/migrate/template.ts b/backend/src/db/migrate/template.ts index 9306ec27c..a7287dd42 100644 --- a/backend/src/db/migrate/template.ts +++ b/backend/src/db/migrate/template.ts @@ -1,11 +1,5 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ + import { getDriver } from '@db/neo4j' export const description = '' diff --git a/backend/src/db/migrations-examples/20200123150105-merge_duplicate_user_accounts.ts b/backend/src/db/migrations-examples/20200123150105-merge_duplicate_user_accounts.ts index 7d4195131..eda3206b4 100644 --- a/backend/src/db/migrations-examples/20200123150105-merge_duplicate_user_accounts.ts +++ b/backend/src/db/migrations-examples/20200123150105-merge_duplicate_user_accounts.ts @@ -1,11 +1,9 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ /* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable promise/prefer-await-to-callbacks */ import { throwError, concat } from 'rxjs' diff --git a/backend/src/db/migrations-examples/20200123150110-merge_duplicate_location_nodes.ts b/backend/src/db/migrations-examples/20200123150110-merge_duplicate_location_nodes.ts index 1b180616b..de73bdaae 100644 --- a/backend/src/db/migrations-examples/20200123150110-merge_duplicate_location_nodes.ts +++ b/backend/src/db/migrations-examples/20200123150110-merge_duplicate_location_nodes.ts @@ -1,11 +1,9 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ /* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable promise/prefer-await-to-callbacks */ import { throwError, concat } from 'rxjs' diff --git a/backend/src/db/migrations-examples/20200127110135-create_muted_relationship_between_existing_blocked_relationships.ts b/backend/src/db/migrations-examples/20200127110135-create_muted_relationship_between_existing_blocked_relationships.ts index a8b6f8179..8be7bad07 100644 --- a/backend/src/db/migrations-examples/20200127110135-create_muted_relationship_between_existing_blocked_relationships.ts +++ b/backend/src/db/migrations-examples/20200127110135-create_muted_relationship_between_existing_blocked_relationships.ts @@ -1,11 +1,5 @@ -/* eslint-disable @typescript-eslint/require-await */ -/* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ + import { getDriver } from '@db/neo4j' export const description = ` diff --git a/backend/src/db/migrations-examples/20200206190233-swap_latitude_with_longitude.ts b/backend/src/db/migrations-examples/20200206190233-swap_latitude_with_longitude.ts index a1eab53fa..f63be216d 100644 --- a/backend/src/db/migrations-examples/20200206190233-swap_latitude_with_longitude.ts +++ b/backend/src/db/migrations-examples/20200206190233-swap_latitude_with_longitude.ts @@ -1,12 +1,8 @@ /* eslint-disable @typescript-eslint/no-floating-promises */ /* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ + import { getDriver } from '@db/neo4j' export const description = ` diff --git a/backend/src/db/migrations-examples/20200207080200-fulltext_index_for_tags.ts b/backend/src/db/migrations-examples/20200207080200-fulltext_index_for_tags.ts index 3fb44c77a..f2e32d6f8 100644 --- a/backend/src/db/migrations-examples/20200207080200-fulltext_index_for_tags.ts +++ b/backend/src/db/migrations-examples/20200207080200-fulltext_index_for_tags.ts @@ -1,11 +1,7 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ import { getDriver } from '@db/neo4j' export const description = diff --git a/backend/src/db/migrations-examples/20200213230248-add_unique_index_to_image_url.ts b/backend/src/db/migrations-examples/20200213230248-add_unique_index_to_image_url.ts index 0af7626a1..81d96f68c 100644 --- a/backend/src/db/migrations-examples/20200213230248-add_unique_index_to_image_url.ts +++ b/backend/src/db/migrations-examples/20200213230248-add_unique_index_to_image_url.ts @@ -1,11 +1,7 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ import { getDriver } from '@db/neo4j' export const description = ` diff --git a/backend/src/db/migrations-examples/20200312140328-bulk_upload_to_s3.ts b/backend/src/db/migrations-examples/20200312140328-bulk_upload_to_s3.ts index 606986d68..0307a2e6e 100644 --- a/backend/src/db/migrations-examples/20200312140328-bulk_upload_to_s3.ts +++ b/backend/src/db/migrations-examples/20200312140328-bulk_upload_to_s3.ts @@ -1,6 +1,4 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ diff --git a/backend/src/db/migrations-examples/20200320200315-refactor_all_images_to_separate_type.ts b/backend/src/db/migrations-examples/20200320200315-refactor_all_images_to_separate_type.ts index 3028b9837..1d24bd141 100644 --- a/backend/src/db/migrations-examples/20200320200315-refactor_all_images_to_separate_type.ts +++ b/backend/src/db/migrations-examples/20200320200315-refactor_all_images_to_separate_type.ts @@ -1,11 +1,6 @@ -/* eslint-disable @typescript-eslint/require-await */ -/* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ + /* eslint-disable no-console */ import { getDriver } from '@db/neo4j' diff --git a/backend/src/db/migrations-examples/20200323140300-remove_deleted_users_obsolete_attributes.ts b/backend/src/db/migrations-examples/20200323140300-remove_deleted_users_obsolete_attributes.ts index d0e0ab5e6..b75324a78 100644 --- a/backend/src/db/migrations-examples/20200323140300-remove_deleted_users_obsolete_attributes.ts +++ b/backend/src/db/migrations-examples/20200323140300-remove_deleted_users_obsolete_attributes.ts @@ -1,11 +1,8 @@ /* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ + import { getDriver } from '@db/neo4j' export const description = diff --git a/backend/src/db/migrations-examples/20200323160336-remove_deleted_posts_obsolete_attributes.ts b/backend/src/db/migrations-examples/20200323160336-remove_deleted_posts_obsolete_attributes.ts index 0bda73770..597eb1d83 100644 --- a/backend/src/db/migrations-examples/20200323160336-remove_deleted_posts_obsolete_attributes.ts +++ b/backend/src/db/migrations-examples/20200323160336-remove_deleted_posts_obsolete_attributes.ts @@ -1,11 +1,8 @@ /* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ + import { getDriver } from '@db/neo4j' export const description = diff --git a/backend/src/db/migrations-examples/20200326160326-remove_dangling_image_urls.ts b/backend/src/db/migrations-examples/20200326160326-remove_dangling_image_urls.ts index 740b1a85e..1109ac623 100644 --- a/backend/src/db/migrations-examples/20200326160326-remove_dangling_image_urls.ts +++ b/backend/src/db/migrations-examples/20200326160326-remove_dangling_image_urls.ts @@ -3,8 +3,7 @@ /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ + /* eslint-disable security/detect-non-literal-fs-filename */ import { existsSync } from 'node:fs' diff --git a/backend/src/db/migrations/1614023644903-add-clickedCount-to-posts.ts b/backend/src/db/migrations/1614023644903-add-clickedCount-to-posts.ts index e1b884de0..7443b4749 100644 --- a/backend/src/db/migrations/1614023644903-add-clickedCount-to-posts.ts +++ b/backend/src/db/migrations/1614023644903-add-clickedCount-to-posts.ts @@ -1,7 +1,5 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ + import { getDriver } from '@db/neo4j' export const description = ` diff --git a/backend/src/db/migrations/1614177130817-add-viewedTeaserCount-to-posts.ts b/backend/src/db/migrations/1614177130817-add-viewedTeaserCount-to-posts.ts index 5ed831b61..b23bf96bf 100644 --- a/backend/src/db/migrations/1614177130817-add-viewedTeaserCount-to-posts.ts +++ b/backend/src/db/migrations/1614177130817-add-viewedTeaserCount-to-posts.ts @@ -1,7 +1,5 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ + import { getDriver } from '@db/neo4j' export const description = ` diff --git a/backend/src/db/migrations/20210506150512-add-donations-node.ts b/backend/src/db/migrations/20210506150512-add-donations-node.ts index 2cba177b7..90f00e26f 100644 --- a/backend/src/db/migrations/20210506150512-add-donations-node.ts +++ b/backend/src/db/migrations/20210506150512-add-donations-node.ts @@ -1,11 +1,5 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ + import { v4 as uuid } from 'uuid' import { getDriver } from '@db/neo4j' diff --git a/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.ts b/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.ts index c7c774c70..5bc6aed17 100644 --- a/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.ts +++ b/backend/src/db/migrations/20210923140939-add-sendNotificationEmails-property-to-all-users.ts @@ -1,11 +1,5 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ + import { getDriver } from '@db/neo4j' export const description = '' diff --git a/backend/src/db/migrations/20220803060819-create_fulltext_indices_and_unique_keys_for_groups.ts b/backend/src/db/migrations/20220803060819-create_fulltext_indices_and_unique_keys_for_groups.ts index 106aec0dc..f06c10984 100644 --- a/backend/src/db/migrations/20220803060819-create_fulltext_indices_and_unique_keys_for_groups.ts +++ b/backend/src/db/migrations/20220803060819-create_fulltext_indices_and_unique_keys_for_groups.ts @@ -1,11 +1,5 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ + import { getDriver } from '@db/neo4j' export const description = ` diff --git a/backend/src/db/migrations/20230320130345-fulltext-search-indexes.ts b/backend/src/db/migrations/20230320130345-fulltext-search-indexes.ts index b12db9964..686d221de 100644 --- a/backend/src/db/migrations/20230320130345-fulltext-search-indexes.ts +++ b/backend/src/db/migrations/20230320130345-fulltext-search-indexes.ts @@ -1,11 +1,5 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ + import { getDriver } from '@db/neo4j' export const description = '' diff --git a/backend/src/db/migrations/20230329150329-article-label-for-posts.ts b/backend/src/db/migrations/20230329150329-article-label-for-posts.ts index 06f05dbd1..44433e56b 100644 --- a/backend/src/db/migrations/20230329150329-article-label-for-posts.ts +++ b/backend/src/db/migrations/20230329150329-article-label-for-posts.ts @@ -1,11 +1,5 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ + import { getDriver } from '@db/neo4j' export const description = 'Add to all existing posts the Article label' diff --git a/backend/src/db/migrations/20230608130637-add-postType-property.ts b/backend/src/db/migrations/20230608130637-add-postType-property.ts index f58e4de97..1e5474064 100644 --- a/backend/src/db/migrations/20230608130637-add-postType-property.ts +++ b/backend/src/db/migrations/20230608130637-add-postType-property.ts @@ -1,11 +1,5 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ + import { getDriver } from '@db/neo4j' export const description = 'Add postType property Article to all posts' diff --git a/backend/src/db/migrations/20231017141022-fix-event-dates.ts b/backend/src/db/migrations/20231017141022-fix-event-dates.ts index 1b92537ba..259e3ff65 100644 --- a/backend/src/db/migrations/20231017141022-fix-event-dates.ts +++ b/backend/src/db/migrations/20231017141022-fix-event-dates.ts @@ -1,12 +1,7 @@ /* eslint-disable @typescript-eslint/no-base-to-string */ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ + import { getDriver } from '@db/neo4j' export const description = ` diff --git a/backend/src/db/migrations/20250331130323-author-observes-own-post.ts b/backend/src/db/migrations/20250331130323-author-observes-own-post.ts index 4411107e7..df6eebf23 100644 --- a/backend/src/db/migrations/20250331130323-author-observes-own-post.ts +++ b/backend/src/db/migrations/20250331130323-author-observes-own-post.ts @@ -1,11 +1,5 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ + import { getDriver } from '@db/neo4j' export const description = ` diff --git a/backend/src/db/migrations/20250331140313-commenter-observes-post.ts b/backend/src/db/migrations/20250331140313-commenter-observes-post.ts index 664c64047..ce1d32bc0 100644 --- a/backend/src/db/migrations/20250331140313-commenter-observes-post.ts +++ b/backend/src/db/migrations/20250331140313-commenter-observes-post.ts @@ -1,11 +1,5 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ + import { getDriver } from '@db/neo4j' export const description = ` diff --git a/backend/src/db/migrations/20250405030454-email-notification-settings.ts b/backend/src/db/migrations/20250405030454-email-notification-settings.ts index afe3a5ed3..eaa9a7a6e 100644 --- a/backend/src/db/migrations/20250405030454-email-notification-settings.ts +++ b/backend/src/db/migrations/20250405030454-email-notification-settings.ts @@ -1,11 +1,5 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ + import { getDriver } from '@db/neo4j' export const description = diff --git a/backend/src/db/migrations/20250414220436-delete-old-badges.ts b/backend/src/db/migrations/20250414220436-delete-old-badges.ts index 6a932543e..d03e14619 100644 --- a/backend/src/db/migrations/20250414220436-delete-old-badges.ts +++ b/backend/src/db/migrations/20250414220436-delete-old-badges.ts @@ -1,11 +1,5 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ + import { getDriver } from '@db/neo4j' export const description = '' diff --git a/backend/src/db/neo4j.ts b/backend/src/db/neo4j.ts index 62594ee2d..dcd19a0ea 100644 --- a/backend/src/db/neo4j.ts +++ b/backend/src/db/neo4j.ts @@ -1,7 +1,5 @@ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ + /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable import/no-named-as-default-member */ import neo4j, { Driver } from 'neo4j-driver' diff --git a/backend/src/db/seed.ts b/backend/src/db/seed.ts index 6e09df986..0e2c2c61d 100644 --- a/backend/src/db/seed.ts +++ b/backend/src/db/seed.ts @@ -28,7 +28,6 @@ if (CONFIG.PRODUCTION && !CONFIG.PRODUCTION_DB_CLEAN_ALLOW) { const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] -/* eslint-disable no-multi-spaces */ ;(async function () { let authenticatedUser = null const driver = getDriver() @@ -1594,4 +1593,3 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] process.exit(1) } })() -/* eslint-enable no-multi-spaces */ diff --git a/backend/src/middleware/notifications/notificationsMiddleware.followed-users.spec.ts b/backend/src/middleware/notifications/notificationsMiddleware.followed-users.spec.ts index 7d311ce95..21d4a14a0 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.followed-users.spec.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.followed-users.spec.ts @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ + import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/middleware/notifications/notificationsMiddleware.ts b/backend/src/middleware/notifications/notificationsMiddleware.ts index b9f5c4284..4926dc94e 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.ts @@ -13,7 +13,6 @@ import { isUserOnline } from '@middleware/helpers/isUserOnline' import { validateNotifyUsers } from '@middleware/validation/validationMiddleware' // eslint-disable-next-line import/no-cycle import { getUnreadRoomsCount } from '@schema/resolvers/rooms' -// eslint-disable-next-line import/no-cycle import { pubsub, NOTIFICATION_ADDED, ROOM_COUNT_UPDATED, CHAT_MESSAGE_ADDED } from '@src/server' import extractMentionedUsers from './mentions/extractMentionedUsers' diff --git a/backend/src/middleware/orderByMiddleware.spec.ts b/backend/src/middleware/orderByMiddleware.spec.ts index e46ee1c1f..b98c21c0b 100644 --- a/backend/src/middleware/orderByMiddleware.spec.ts +++ b/backend/src/middleware/orderByMiddleware.spec.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' diff --git a/backend/src/models/Category.ts b/backend/src/models/Category.ts index f61d5aaab..9a3f47fd0 100644 --- a/backend/src/models/Category.ts +++ b/backend/src/models/Category.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { v4 as uuid } from 'uuid' export default { diff --git a/backend/src/models/Comment.ts b/backend/src/models/Comment.ts index f05cb7ccc..f4548f0c2 100644 --- a/backend/src/models/Comment.ts +++ b/backend/src/models/Comment.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { v4 as uuid } from 'uuid' export default { diff --git a/backend/src/models/Donations.ts b/backend/src/models/Donations.ts index 61113702d..742bfb569 100644 --- a/backend/src/models/Donations.ts +++ b/backend/src/models/Donations.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { v4 as uuid } from 'uuid' export default { diff --git a/backend/src/models/Group.ts b/backend/src/models/Group.ts index cff388a0a..a75ad518f 100644 --- a/backend/src/models/Group.ts +++ b/backend/src/models/Group.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { v4 as uuid } from 'uuid' export default { diff --git a/backend/src/models/Post.ts b/backend/src/models/Post.ts index 466e8a21d..75081b728 100644 --- a/backend/src/models/Post.ts +++ b/backend/src/models/Post.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { v4 as uuid } from 'uuid' export default { diff --git a/backend/src/models/Report.ts b/backend/src/models/Report.ts index 07e8a79c1..3e001746b 100644 --- a/backend/src/models/Report.ts +++ b/backend/src/models/Report.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { v4 as uuid } from 'uuid' export default { diff --git a/backend/src/models/SocialMedia.ts b/backend/src/models/SocialMedia.ts index 86f2f90be..6010c97bb 100644 --- a/backend/src/models/SocialMedia.ts +++ b/backend/src/models/SocialMedia.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { v4 as uuid } from 'uuid' export default { diff --git a/backend/src/models/User.spec.ts b/backend/src/models/User.spec.ts index 272ad2df3..cea2d4db0 100644 --- a/backend/src/models/User.spec.ts +++ b/backend/src/models/User.spec.ts @@ -1,9 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ + import { cleanDatabase } from '@db/factories' import { getNeode, getDriver } from '@db/neo4j' diff --git a/backend/src/models/User.ts b/backend/src/models/User.ts index 611b6a984..77a37c3c1 100644 --- a/backend/src/models/User.ts +++ b/backend/src/models/User.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { v4 as uuid } from 'uuid' export default { diff --git a/backend/src/schema/resolvers/follow.ts b/backend/src/schema/resolvers/follow.ts index 809d77760..d08f243b1 100644 --- a/backend/src/schema/resolvers/follow.ts +++ b/backend/src/schema/resolvers/follow.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ + /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ + /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { getNeode } from '@db/neo4j' diff --git a/backend/src/schema/resolvers/helpers/Resolver.ts b/backend/src/schema/resolvers/helpers/Resolver.ts index 2d7faa7b7..71d7602a4 100644 --- a/backend/src/schema/resolvers/helpers/Resolver.ts +++ b/backend/src/schema/resolvers/helpers/Resolver.ts @@ -7,8 +7,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable security/detect-object-injection */ -import log from './databaseLogger' - export const undefinedToNullResolver = (list) => { const resolvers = {} list.forEach((key) => { @@ -41,7 +39,6 @@ export default function Resolver(type, options: any = {}) { RETURN related {.*} as related ` const result = await txc.run(cypher, { id, cypherParams }) - log(result) return result.records.map((r) => r.get('related')) }) try { @@ -66,7 +63,6 @@ export default function Resolver(type, options: any = {}) { const nodeCondition = condition.replace('this', 'this {id: $id}') const cypher = `${nodeCondition} as ${key}` const result = await txc.run(cypher, { id, cypherParams }) - log(result) const [response] = result.records.map((r) => r.get(key)) return response }) @@ -93,7 +89,6 @@ export default function Resolver(type, options: any = {}) { RETURN COUNT(DISTINCT(related)) as count ` const result = await txc.run(cypher, { id, cypherParams }) - log(result) const [response] = result.records.map((r) => r.get('count').toNumber()) return response }) diff --git a/backend/src/schema/resolvers/helpers/databaseLogger.ts b/backend/src/schema/resolvers/helpers/databaseLogger.ts deleted file mode 100644 index ad1de5828..000000000 --- a/backend/src/schema/resolvers/helpers/databaseLogger.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable import/no-named-as-default */ -// eslint-disable-next-line import/no-extraneous-dependencies -import Debug from 'debug' - -const debugCypher = Debug('human-connection:neo4j:cypher') -const debugStats = Debug('human-connection:neo4j:stats') - -export default function log(response) { - const { counters, resultConsumedAfter, resultAvailableAfter, query } = response.summary - const { text, parameters } = query - debugCypher('%s', text) - debugCypher('%o', parameters) - debugStats('%o', counters) - debugStats('%o', { - resultConsumedAfter: resultConsumedAfter.toNumber(), - resultAvailableAfter: resultAvailableAfter.toNumber(), - }) -} diff --git a/backend/src/schema/resolvers/moderation.ts b/backend/src/schema/resolvers/moderation.ts index 6fe8637c6..bcdb3992a 100644 --- a/backend/src/schema/resolvers/moderation.ts +++ b/backend/src/schema/resolvers/moderation.ts @@ -2,8 +2,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -import log from './helpers/databaseLogger' - export default { Mutation: { review: async (_object, params, context, _resolveInfo) => { @@ -31,7 +29,6 @@ export default { moderatorId: moderator.id, dateTime: new Date().toISOString(), }) - log(reviewTransactionResponse) return reviewTransactionResponse.records.map((record) => record.get('review')) }) const [reviewed] = await reviewWriteTxResultPromise diff --git a/backend/src/schema/resolvers/notifications.ts b/backend/src/schema/resolvers/notifications.ts index 6151d305e..0168558c3 100644 --- a/backend/src/schema/resolvers/notifications.ts +++ b/backend/src/schema/resolvers/notifications.ts @@ -8,8 +8,6 @@ import { withFilter } from 'graphql-subscriptions' import { pubsub, NOTIFICATION_ADDED } from '@src/server' -import log from './helpers/databaseLogger' - export default { Subscription: { notificationAdded: { @@ -76,7 +74,6 @@ export default { `, { id: currentUser.id }, ) - log(notificationsTransactionResponse) return notificationsTransactionResponse.records.map((record) => record.get('notification')) }) try { @@ -106,7 +103,6 @@ export default { `, { resourceId: args.id, id: currentUser.id }, ) - log(markNotificationAsReadTransactionResponse) return markNotificationAsReadTransactionResponse.records.map((record) => record.get('notification'), ) @@ -136,7 +132,6 @@ export default { `, { id: currentUser.id }, ) - log(markAllNotificationAsReadTransactionResponse) return markAllNotificationAsReadTransactionResponse.records.map((record) => record.get('notification'), ) diff --git a/backend/src/schema/resolvers/passwordReset.ts b/backend/src/schema/resolvers/passwordReset.ts index 6e74ac710..3159d7006 100644 --- a/backend/src/schema/resolvers/passwordReset.ts +++ b/backend/src/schema/resolvers/passwordReset.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/await-thenable */ + /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ diff --git a/backend/src/schema/resolvers/registration.spec.ts b/backend/src/schema/resolvers/registration.spec.ts index deaee0d08..f19c6bf01 100644 --- a/backend/src/schema/resolvers/registration.spec.ts +++ b/backend/src/schema/resolvers/registration.spec.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ diff --git a/backend/src/schema/resolvers/reports.ts b/backend/src/schema/resolvers/reports.ts index 35e250f48..b8886c48f 100644 --- a/backend/src/schema/resolvers/reports.ts +++ b/backend/src/schema/resolvers/reports.ts @@ -3,8 +3,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -import log from './helpers/databaseLogger' - export default { Mutation: { fileReport: async (_parent, params, context, _resolveInfo) => { @@ -33,7 +31,6 @@ export default { reasonDescription, }, ) - log(fileReportTransactionResponse) return fileReportTransactionResponse.records.map((record) => record.get('filedReport')) }) try { @@ -106,7 +103,6 @@ export default { ${offset} ${limit} `, ) - log(reportsTransactionResponse) return reportsTransactionResponse.records.map((record) => record.get('report')) }) try { @@ -131,7 +127,6 @@ export default { `, { id }, ) - log(filedReportsTransactionResponse) return filedReportsTransactionResponse.records.map((record) => ({ submitter: record.get('submitter').properties, filed: record.get('filed').properties, @@ -166,7 +161,6 @@ export default { `, { id }, ) - log(reviewedReportsTransactionResponse) return reviewedReportsTransactionResponse.records.map((record) => ({ review: record.get('review').properties, moderator: record.get('moderator').properties, diff --git a/backend/src/schema/resolvers/searches.ts b/backend/src/schema/resolvers/searches.ts index 845a070a5..34fc11709 100644 --- a/backend/src/schema/resolvers/searches.ts +++ b/backend/src/schema/resolvers/searches.ts @@ -4,7 +4,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import log from './helpers/databaseLogger' import { queryString } from './searches/queryString' // see http://lucene.apache.org/core/8_3_1/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#package.description @@ -133,7 +132,6 @@ const getSearchResults = async (context, setup, params, resultCallback = searchR const session = context.driver.session() try { const results = await searchResultPromise(session, setup, params) - log(results) return resultCallback(results) } finally { session.close() diff --git a/backend/src/schema/resolvers/shout.ts b/backend/src/schema/resolvers/shout.ts index 0a7ec6a39..f0b5885eb 100644 --- a/backend/src/schema/resolvers/shout.ts +++ b/backend/src/schema/resolvers/shout.ts @@ -2,8 +2,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -import log from './helpers/databaseLogger' - export default { Mutation: { shout: async (_object, params, context, _resolveInfo) => { @@ -25,7 +23,6 @@ export default { userId: context.user.id, }, ) - log(shoutTransactionResponse) return shoutTransactionResponse.records.map((record) => record.get('isShouted')) }) const [isShouted] = await shoutWriteTxResultPromise @@ -53,7 +50,6 @@ export default { userId: context.user.id, }, ) - log(unshoutTransactionResponse) return unshoutTransactionResponse.records.map((record) => record.get('isShouted')) }) const [isShouted] = await unshoutWriteTxResultPromise diff --git a/backend/src/schema/resolvers/socialMedia.ts b/backend/src/schema/resolvers/socialMedia.ts index ad2bdabe5..952e4a27e 100644 --- a/backend/src/schema/resolvers/socialMedia.ts +++ b/backend/src/schema/resolvers/socialMedia.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ + /* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ + /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { getNeode } from '@db/neo4j' diff --git a/backend/src/schema/resolvers/statistics.ts b/backend/src/schema/resolvers/statistics.ts index e2b93bbea..f7af390bf 100644 --- a/backend/src/schema/resolvers/statistics.ts +++ b/backend/src/schema/resolvers/statistics.ts @@ -3,8 +3,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable security/detect-object-injection */ -import log from './helpers/databaseLogger' - export default { Query: { statistics: async (_parent, _args, { driver }) => { @@ -28,7 +26,6 @@ export default { RETURN labels, relTypesCount `, ) - log(statisticsTransactionResponse) return statisticsTransactionResponse.records.map((record) => { return { ...record.get('labels'), diff --git a/backend/src/schema/resolvers/user_management.ts b/backend/src/schema/resolvers/user_management.ts index 6b84f7256..7bea1f53c 100644 --- a/backend/src/schema/resolvers/user_management.ts +++ b/backend/src/schema/resolvers/user_management.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/await-thenable */ + /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ @@ -12,7 +12,6 @@ import { neo4jgraphql } from 'neo4j-graphql-js' import { getNeode } from '@db/neo4j' import encode from '@jwt/encode' -import log from './helpers/databaseLogger' import normalizeEmail from './helpers/normalizeEmail' const neode = getNeode() @@ -38,7 +37,6 @@ export default { `, { userEmail: email }, ) - log(loginTransactionResponse) return loginTransactionResponse.records.map((record) => record.get('user')) }) const [currentUser] = await loginReadTxResultPromise diff --git a/backend/src/schema/resolvers/users.ts b/backend/src/schema/resolvers/users.ts index c165e8e44..f549e79a3 100644 --- a/backend/src/schema/resolvers/users.ts +++ b/backend/src/schema/resolvers/users.ts @@ -12,7 +12,6 @@ import { TROPHY_BADGES_SELECTED_MAX } from '@constants/badges' import { getNeode } from '@db/neo4j' import { defaultTrophyBadge, defaultVerificationBadge } from './badges' -import log from './helpers/databaseLogger' import Resolver from './helpers/Resolver' import { mergeImage, deleteImage } from './images/images' import { createOrUpdateLocations } from './users/location' @@ -279,7 +278,6 @@ export default { `, { userId }, ) - log(deleteUserTransactionResponse) const [user] = deleteUserTransactionResponse.records.map((record) => record.get('user')) await deleteImage(user, 'AVATAR_IMAGE', { transaction }) return user diff --git a/backend/src/schema/resolvers/users/location.ts b/backend/src/schema/resolvers/users/location.ts index d32c03cd2..6dfaede4e 100644 --- a/backend/src/schema/resolvers/users/location.ts +++ b/backend/src/schema/resolvers/users/location.ts @@ -8,17 +8,12 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable promise/avoid-new */ /* eslint-disable promise/prefer-await-to-callbacks */ -/* eslint-disable import/no-named-as-default */ import { UserInputError } from 'apollo-server' -// eslint-disable-next-line import/no-extraneous-dependencies -import Debug from 'debug' import request from 'request' import CONFIG from '@config/index' import asyncForEach from '@helpers/asyncForEach' -const debug = Debug('human-connection:location') - const fetch = (url) => { return new Promise((resolve, reject) => { request(url, function (error, response, body) { @@ -93,8 +88,6 @@ export const createOrUpdateLocations = async (nodeLabel, nodeId, locationName, s }&types=region,place,country,address&language=${locales.join(',')}`, ) - debug(res) - if (!res?.features?.[0]) { throw new UserInputError('locationName is invalid') } diff --git a/backend/test/setup.ts b/backend/test/setup.ts index 594b9763d..128830f13 100644 --- a/backend/test/setup.ts +++ b/backend/test/setup.ts @@ -1,10 +1,2 @@ -// Polyfill missing encoders in jsdom -// https://stackoverflow.com/questions/68468203/why-am-i-getting-textencoder-is-not-defined-in-jest -// import { TextEncoder, TextDecoder } from 'util' - -// global.TextEncoder = TextEncoder -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -// global.TextDecoder = TextDecoder as any - // Metascraper takes longer nowadays, double time // jest.setTimeout(10000) diff --git a/backend/yarn.lock b/backend/yarn.lock index 3f0aa3990..c972f9925 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -2126,6 +2126,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/uuid@~9.0.1": + version "9.0.8" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba" + integrity sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA== + "@types/ws@^7.0.0": version "7.2.5" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.2.5.tgz#513f28b04a1ea1aa9dc2cad3f26e8e37c88aae49" From 2fd138697f8065cd3ef86d96272a129607244449 Mon Sep 17 00:00:00 2001 From: sebastian2357 <80636200+sebastian2357@users.noreply.github.com> Date: Fri, 25 Apr 2025 18:55:46 +0200 Subject: [PATCH 38/54] feat(webapp): badges UI (#8426) - New badge UI, including editor. - Adds config to enable/disable badges. --------- Co-authored-by: Sebastian Stein Co-authored-by: Maximilian Harz --- webapp/components/BadgeSelection.spec.js | 76 + webapp/components/BadgeSelection.vue | 102 + webapp/components/Badges.spec.js | 119 +- webapp/components/Badges.vue | 172 +- .../__snapshots__/BadgeSelection.spec.js.snap | 84 + .../__snapshots__/Badges.spec.js.snap | 165 ++ webapp/config/index.js | 1 + webapp/graphql/Fragments.js | 8 +- webapp/graphql/User.js | 56 + webapp/locales/de.json | 10 + webapp/locales/en.json | 10 + webapp/locales/es.json | 10 + webapp/locales/fr.json | 10 + webapp/locales/it.json | 10 + webapp/locales/nl.json | 10 + webapp/locales/pl.json | 10 + webapp/locales/pt.json | 10 + webapp/locales/ru.json | 10 + webapp/package.json | 1 + .../pages/__snapshots__/settings.spec.js.snap | 427 +++ .../users/__snapshots__/index.spec.js.snap | 847 ++++++ webapp/pages/admin/users/index.spec.js | 236 +- webapp/pages/admin/users/index.vue | 11 +- .../_id/__snapshots__/_slug.spec.js.snap | 2442 +++++++++++++++++ webapp/pages/profile/_id/_slug.spec.js | 176 +- webapp/pages/profile/_id/_slug.vue | 17 +- webapp/pages/settings.spec.js | 36 +- webapp/pages/settings.vue | 11 +- .../__snapshots__/badges.spec.js.snap | 429 +++ webapp/pages/settings/badges.spec.js | 302 ++ webapp/pages/settings/badges.vue | 176 ++ webapp/static/img/badges/stars.svg | 11 + webapp/yarn.lock | 33 + 33 files changed, 5820 insertions(+), 208 deletions(-) create mode 100644 webapp/components/BadgeSelection.spec.js create mode 100644 webapp/components/BadgeSelection.vue create mode 100644 webapp/components/__snapshots__/BadgeSelection.spec.js.snap create mode 100644 webapp/components/__snapshots__/Badges.spec.js.snap create mode 100644 webapp/pages/__snapshots__/settings.spec.js.snap create mode 100644 webapp/pages/admin/users/__snapshots__/index.spec.js.snap create mode 100644 webapp/pages/profile/_id/__snapshots__/_slug.spec.js.snap create mode 100644 webapp/pages/settings/__snapshots__/badges.spec.js.snap create mode 100644 webapp/pages/settings/badges.spec.js create mode 100644 webapp/pages/settings/badges.vue create mode 100644 webapp/static/img/badges/stars.svg diff --git a/webapp/components/BadgeSelection.spec.js b/webapp/components/BadgeSelection.spec.js new file mode 100644 index 000000000..78f00b87a --- /dev/null +++ b/webapp/components/BadgeSelection.spec.js @@ -0,0 +1,76 @@ +import { render, screen, fireEvent } from '@testing-library/vue' +import BadgeSelection from './BadgeSelection.vue' + +const localVue = global.localVue + +describe('Badges.vue', () => { + const Wrapper = (propsData) => { + return render(BadgeSelection, { + propsData, + localVue, + }) + } + + describe('without badges', () => { + it('renders', () => { + const wrapper = Wrapper({ badges: [] }) + expect(wrapper.container).toMatchSnapshot() + }) + }) + + describe('with badges', () => { + const badges = [ + { + id: '1', + icon: '/path/to/some/icon', + isDefault: false, + description: 'Some description', + }, + { + id: '2', + icon: '/path/to/another/icon', + isDefault: true, + description: 'Another description', + }, + { + id: '3', + icon: '/path/to/third/icon', + isDefault: false, + description: 'Third description', + }, + ] + + let wrapper + + beforeEach(() => { + wrapper = Wrapper({ badges }) + }) + + it('renders', () => { + expect(wrapper.container).toMatchSnapshot() + }) + + describe('clicking on a badge', () => { + beforeEach(async () => { + const badge = screen.getByText(badges[1].description) + await fireEvent.click(badge) + }) + + it('emits badge-selected with badge', async () => { + expect(wrapper.emitted()['badge-selected']).toEqual([[badges[1]]]) + }) + }) + + describe('clicking twice on a badge', () => { + beforeEach(async () => { + const badge = screen.getByText(badges[1].description) + await fireEvent.click(badge) + await fireEvent.click(badge) + }) + + it('emits badge-selected with null', async () => { + expect(wrapper.emitted()['badge-selected']).toEqual([[badges[1]], [null]]) + }) + }) + }) +}) diff --git a/webapp/components/BadgeSelection.vue b/webapp/components/BadgeSelection.vue new file mode 100644 index 000000000..a6554d779 --- /dev/null +++ b/webapp/components/BadgeSelection.vue @@ -0,0 +1,102 @@ + + + + + diff --git a/webapp/components/Badges.spec.js b/webapp/components/Badges.spec.js index d19c2beb2..ae15e0b0a 100644 --- a/webapp/components/Badges.spec.js +++ b/webapp/components/Badges.spec.js @@ -1,29 +1,114 @@ -import { shallowMount } from '@vue/test-utils' +import { render, screen, fireEvent } from '@testing-library/vue' import Badges from './Badges.vue' +const localVue = global.localVue + describe('Badges.vue', () => { - let propsData + const Wrapper = (propsData) => { + return render(Badges, { + propsData, + localVue, + }) + } - beforeEach(() => { - propsData = {} - }) - - describe('shallowMount', () => { - const Wrapper = () => { - return shallowMount(Badges, { propsData }) - } - - it('has class "hc-badges"', () => { - expect(Wrapper().find('.hc-badges').exists()).toBe(true) + describe('without badges', () => { + it('renders in presentation mode', () => { + const wrapper = Wrapper({ badges: [], selectionMode: false }) + expect(wrapper.container).toMatchSnapshot() }) - describe('given a badge', () => { + it('renders in selection mode', () => { + const wrapper = Wrapper({ badges: [], selectionMode: true }) + expect(wrapper.container).toMatchSnapshot() + }) + }) + + describe('with badges', () => { + const badges = [ + { + id: '1', + icon: '/path/to/some/icon', + isDefault: false, + description: 'Some description', + }, + { + id: '2', + icon: '/path/to/another/icon', + isDefault: true, + description: 'Another description', + }, + { + id: '3', + icon: '/path/to/third/icon', + isDefault: false, + description: 'Third description', + }, + ] + + describe('in presentation mode', () => { + let wrapper + beforeEach(() => { - propsData.badges = [{ id: '1', icon: '/path/to/some/icon' }] + wrapper = Wrapper({ badges, scale: 1.2, selectionMode: false }) }) - it('proxies badge icon, which is just a URL without metadata', () => { - expect(Wrapper().find('img[src="/api/path/to/some/icon"]').exists()).toBe(true) + it('renders', () => { + expect(wrapper.container).toMatchSnapshot() + }) + + it('clicking on second badge does nothing', async () => { + const badge = screen.getByTitle(badges[1].description) + await fireEvent.click(badge) + expect(wrapper.emitted()).toEqual({}) + }) + }) + + describe('in selection mode', () => { + let wrapper + + beforeEach(() => { + wrapper = Wrapper({ badges, scale: 1.2, selectionMode: true }) + }) + + it('renders', () => { + expect(wrapper.container).toMatchSnapshot() + }) + + it('clicking on first badge does nothing', async () => { + const badge = screen.getByTitle(badges[0].description) + await fireEvent.click(badge) + expect(wrapper.emitted()).toEqual({}) + }) + + describe('clicking on second badge', () => { + beforeEach(async () => { + const badge = screen.getByTitle(badges[1].description) + await fireEvent.click(badge) + }) + + it('selects badge', () => { + expect(wrapper.container).toMatchSnapshot() + }) + + it('emits badge-selected with index', async () => { + expect(wrapper.emitted()['badge-selected']).toEqual([[1]]) + }) + }) + + describe('clicking twice on second badge', () => { + beforeEach(async () => { + const badge = screen.getByTitle(badges[1].description) + await fireEvent.click(badge) + await fireEvent.click(badge) + }) + + it('deselects badge', () => { + expect(wrapper.container).toMatchSnapshot() + }) + + it('emits badge-selected with null', async () => { + expect(wrapper.emitted()['badge-selected']).toEqual([[1], [null]]) + }) }) }) }) diff --git a/webapp/components/Badges.vue b/webapp/components/Badges.vue index d569452c7..ca5c4f0ef 100644 --- a/webapp/components/Badges.vue +++ b/webapp/components/Badges.vue @@ -1,69 +1,171 @@ diff --git a/webapp/components/__snapshots__/BadgeSelection.spec.js.snap b/webapp/components/__snapshots__/BadgeSelection.spec.js.snap new file mode 100644 index 000000000..a31d547c9 --- /dev/null +++ b/webapp/components/__snapshots__/BadgeSelection.spec.js.snap @@ -0,0 +1,84 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Badges.vue with badges renders 1`] = ` +
+
+ + + +
+
+`; + +exports[`Badges.vue without badges renders 1`] = ` +
+
+
+`; diff --git a/webapp/components/__snapshots__/Badges.spec.js.snap b/webapp/components/__snapshots__/Badges.spec.js.snap new file mode 100644 index 000000000..6ea612a76 --- /dev/null +++ b/webapp/components/__snapshots__/Badges.spec.js.snap @@ -0,0 +1,165 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Badges.vue with badges in presentation mode renders 1`] = ` +
+
+
+ +
+
+ +
+
+ +
+
+
+`; + +exports[`Badges.vue with badges in selection mode clicking on second badge selects badge 1`] = ` +
+
+ + + +
+
+`; + +exports[`Badges.vue with badges in selection mode clicking twice on second badge deselects badge 1`] = ` +
+
+ + + +
+
+`; + +exports[`Badges.vue with badges in selection mode renders 1`] = ` +
+
+ + + +
+
+`; + +exports[`Badges.vue without badges renders in presentation mode 1`] = ` +
+
+
+`; + +exports[`Badges.vue without badges renders in selection mode 1`] = ` +
+
+
+`; diff --git a/webapp/config/index.js b/webapp/config/index.js index 5da17010b..fb275a8ec 100644 --- a/webapp/config/index.js +++ b/webapp/config/index.js @@ -35,6 +35,7 @@ const options = { COOKIE_EXPIRE_TIME: process.env.COOKIE_EXPIRE_TIME || 730, // Two years by default COOKIE_HTTPS_ONLY: process.env.COOKIE_HTTPS_ONLY || process.env.NODE_ENV === 'production', // ensure true in production if not set explicitly CATEGORIES_ACTIVE: process.env.CATEGORIES_ACTIVE === 'true' || false, + BADGES_ENABLED: process.env.BADGES_ENABLED === 'true' || false, } const CONFIG = { diff --git a/webapp/graphql/Fragments.js b/webapp/graphql/Fragments.js index d0ad8a0fe..77af830e8 100644 --- a/webapp/graphql/Fragments.js +++ b/webapp/graphql/Fragments.js @@ -26,9 +26,15 @@ export const locationFragment = (lang) => gql` export const badgesFragment = gql` fragment badges on User { - badgeTrophies { + badgeTrophiesSelected { id icon + description + } + badgeVerification { + id + icon + description } } ` diff --git a/webapp/graphql/User.js b/webapp/graphql/User.js index 147e93c6f..75342ef2a 100644 --- a/webapp/graphql/User.js +++ b/webapp/graphql/User.js @@ -405,6 +405,22 @@ export const currentUserQuery = gql` query { currentUser { ...user + badgeTrophiesSelected { + id + icon + description + isDefault + } + badgeTrophiesUnused { + id + icon + description + } + badgeVerification { + id + icon + description + } email role about @@ -466,3 +482,43 @@ export const userDataQuery = (i18n) => { } ` } + +export const setTrophyBadgeSelected = gql` + mutation ($slot: Int!, $badgeId: ID) { + setTrophyBadgeSelected(slot: $slot, badgeId: $badgeId) { + badgeTrophiesCount + badgeTrophiesSelected { + id + icon + description + isDefault + } + badgeTrophiesUnused { + id + icon + description + } + badgeTrophiesUnusedCount + } + } +` + +export const resetTrophyBadgesSelected = gql` + mutation { + resetTrophyBadgesSelected { + badgeTrophiesCount + badgeTrophiesSelected { + id + icon + description + isDefault + } + badgeTrophiesUnused { + id + icon + description + } + badgeTrophiesUnusedCount + } + } +` diff --git a/webapp/locales/de.json b/webapp/locales/de.json index ce122672d..4fe39ce24 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -957,6 +957,16 @@ "title": "Suchergebnisse" }, "settings": { + "badges": { + "click-to-select": "Klicke auf einen freien Platz, um eine Badge hinzufügen.", + "click-to-use": "Klicke auf eine Badge, um sie zu platzieren.", + "description": "Hier hast du die Möglichkeit zu entscheiden, wie deine bereits erworbenen Badges in deinem Profil gezeigt werden sollen.", + "name": "Badges", + "no-badges-available": "Im Moment stehen dir keine Badges zur Verfügung, die du hinzufügen könntest.", + "remove": "Badge entfernen", + "success-update": "Deine Badges wurden erfolgreich gespeichert.", + "verification": "Dies ist deine Verifikations-Badge und kann nicht geändert werden." + }, "blocked-users": { "block": "Nutzer blockieren", "columns": { diff --git a/webapp/locales/en.json b/webapp/locales/en.json index f178da549..bdd9cdefb 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -957,6 +957,16 @@ "title": "Search Results" }, "settings": { + "badges": { + "click-to-select": "Click on an empty space to add a badge.", + "click-to-use": "Click on a badge to use it in the selected slot.", + "description": "Here you can choose how to display your earned badges on your profile.", + "name": "Badges", + "no-badges-available": "You currently don't have any badges available to add.", + "remove": "Remove Badge", + "success-update": "Your badges have been updated successfully.", + "verification": "This is your verification badge and cannot be changed." + }, "blocked-users": { "block": "Block user", "columns": { diff --git a/webapp/locales/es.json b/webapp/locales/es.json index 31f2cc5f4..60e65ca20 100644 --- a/webapp/locales/es.json +++ b/webapp/locales/es.json @@ -957,6 +957,16 @@ "title": null }, "settings": { + "badges": { + "click-to-select": null, + "click-to-use": null, + "description": null, + "name": null, + "no-badges-available": null, + "remove": null, + "success-update": null, + "verification": null + }, "blocked-users": { "block": "Bloquear usuario", "columns": { diff --git a/webapp/locales/fr.json b/webapp/locales/fr.json index 4bbca2b82..e7b4fcd4a 100644 --- a/webapp/locales/fr.json +++ b/webapp/locales/fr.json @@ -957,6 +957,16 @@ "title": null }, "settings": { + "badges": { + "click-to-select": null, + "click-to-use": null, + "description": null, + "name": null, + "no-badges-available": null, + "remove": null, + "success-update": null, + "verification": null + }, "blocked-users": { "block": "Bloquer l'utilisateur", "columns": { diff --git a/webapp/locales/it.json b/webapp/locales/it.json index 21bfaa859..703b27fff 100644 --- a/webapp/locales/it.json +++ b/webapp/locales/it.json @@ -957,6 +957,16 @@ "title": null }, "settings": { + "badges": { + "click-to-select": null, + "click-to-use": null, + "description": null, + "name": null, + "no-badges-available": null, + "remove": null, + "success-update": null, + "verification": null + }, "blocked-users": { "block": null, "columns": { diff --git a/webapp/locales/nl.json b/webapp/locales/nl.json index f67518c21..f6a53d0c8 100644 --- a/webapp/locales/nl.json +++ b/webapp/locales/nl.json @@ -957,6 +957,16 @@ "title": null }, "settings": { + "badges": { + "click-to-select": null, + "click-to-use": null, + "description": null, + "name": null, + "no-badges-available": null, + "remove": null, + "success-update": null, + "verification": null + }, "blocked-users": { "block": null, "columns": { diff --git a/webapp/locales/pl.json b/webapp/locales/pl.json index 4c6a96a5f..4e928f417 100644 --- a/webapp/locales/pl.json +++ b/webapp/locales/pl.json @@ -957,6 +957,16 @@ "title": null }, "settings": { + "badges": { + "click-to-select": null, + "click-to-use": null, + "description": null, + "name": null, + "no-badges-available": null, + "remove": null, + "success-update": null, + "verification": null + }, "blocked-users": { "block": null, "columns": { diff --git a/webapp/locales/pt.json b/webapp/locales/pt.json index 7d5ad52c1..005eb77f4 100644 --- a/webapp/locales/pt.json +++ b/webapp/locales/pt.json @@ -957,6 +957,16 @@ "title": null }, "settings": { + "badges": { + "click-to-select": null, + "click-to-use": null, + "description": null, + "name": null, + "no-badges-available": null, + "remove": null, + "success-update": null, + "verification": null + }, "blocked-users": { "block": "Bloquear usuário", "columns": { diff --git a/webapp/locales/ru.json b/webapp/locales/ru.json index 3a394d6ff..bc862bcc2 100644 --- a/webapp/locales/ru.json +++ b/webapp/locales/ru.json @@ -957,6 +957,16 @@ "title": null }, "settings": { + "badges": { + "click-to-select": null, + "click-to-use": null, + "description": null, + "name": null, + "no-badges-available": null, + "remove": null, + "success-update": null, + "verification": null + }, "blocked-users": { "block": "Блокировать", "columns": { diff --git a/webapp/package.json b/webapp/package.json index d18a88408..d11ffd4ab 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -79,6 +79,7 @@ "@storybook/addon-actions": "^5.3.21", "@storybook/addon-notes": "^5.3.18", "@storybook/vue": "~7.4.0", + "@testing-library/jest-dom": "^6.6.3", "@testing-library/vue": "5", "@vue/cli-shared-utils": "~4.3.1", "@vue/eslint-config-prettier": "~6.0.0", diff --git a/webapp/pages/__snapshots__/settings.spec.js.snap b/webapp/pages/__snapshots__/settings.spec.js.snap new file mode 100644 index 000000000..6672e41af --- /dev/null +++ b/webapp/pages/__snapshots__/settings.spec.js.snap @@ -0,0 +1,427 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`settings.vue given badges are disabled renders 1`] = ` +
+
+
+

+ +

+
+ +
+ +
+ + +
+ + + +
+
+
+
+`; + +exports[`settings.vue given badges are enabled renders 1`] = ` +
+
+
+

+ +

+
+ +
+ +
+ + +
+ + + +
+
+
+
+`; diff --git a/webapp/pages/admin/users/__snapshots__/index.spec.js.snap b/webapp/pages/admin/users/__snapshots__/index.spec.js.snap new file mode 100644 index 000000000..b72a2617f --- /dev/null +++ b/webapp/pages/admin/users/__snapshots__/index.spec.js.snap @@ -0,0 +1,847 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Users given badges are disabled renders 1`] = ` +
+
+

+ admin.users.name +

+ +
+
+
+
+ +
+
+ + + + + +
+ + +
+ + + +
+
+ +
+ +
+
+
+ + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + admin.users.table.columns.number + + + + admin.users.table.columns.name + + + + admin.users.table.columns.email + + + + admin.users.table.columns.slug + + + + admin.users.table.columns.createdAt + + + + 🖉 + + + + 🗨 + + + + ❤ + + + + admin.users.table.columns.role + +
+ NaN. + + + + User + + + + + + user@example.org + + + + + + user + + + + + + + + + + + + + + + + + + + +
+ NaN. + + + + User + + + + + + user2@example.org + + + + + + user + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + + +
+ + +
+
+`; + +exports[`Users given badges are enabled renders 1`] = ` +
+
+

+ admin.users.name +

+ +
+
+
+
+ +
+
+ + + + + +
+ + +
+ + + +
+
+ +
+ +
+
+
+ + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + admin.users.table.columns.number + + + + admin.users.table.columns.name + + + + admin.users.table.columns.email + + + + admin.users.table.columns.slug + + + + admin.users.table.columns.createdAt + + + + 🖉 + + + + 🗨 + + + + ❤ + + + + admin.users.table.columns.role + + + + admin.users.table.columns.badges + +
+ NaN. + + + + User + + + + + + user@example.org + + + + + + user + + + + + + + + + + + + + + + + + + + + + + + admin.users.table.edit + + +
+ NaN. + + + + User + + + + + + user2@example.org + + + + + + user + + + + + + + + + + + + + + + + + + + + + + + admin.users.table.edit + + +
+
+ +
+ + + + + +
+ + +
+
+`; diff --git a/webapp/pages/admin/users/index.spec.js b/webapp/pages/admin/users/index.spec.js index 8d6b923c5..85e8789b8 100644 --- a/webapp/pages/admin/users/index.spec.js +++ b/webapp/pages/admin/users/index.spec.js @@ -10,11 +10,9 @@ const stubs = { describe('Users', () => { let wrapper - let Wrapper - let getters const mocks = { - $t: jest.fn(), + $t: jest.fn((t) => t), $apollo: { loading: false, mutate: jest @@ -38,116 +36,154 @@ describe('Users', () => { }, } - describe('mount', () => { - getters = { - 'auth/isAdmin': () => true, - 'auth/user': () => { - return { id: 'admin' } - }, - } + const getters = { + 'auth/isAdmin': () => true, + 'auth/user': () => { + return { id: 'admin' } + }, + } - Wrapper = () => { - const store = new Vuex.Store({ getters }) - return mount(Users, { - mocks, - localVue, - store, - stubs, - }) - } + const Wrapper = () => { + const store = new Vuex.Store({ getters }) + return mount(Users, { + mocks, + localVue, + store, + stubs, + data: () => ({ + User: [ + { + id: 'user', + email: 'user@example.org', + name: 'User', + role: 'moderator', + slug: 'user', + }, + { + id: 'user2', + email: 'user2@example.org', + name: 'User', + role: 'moderator', + slug: 'user', + }, + ], + }), + }) + } + + describe('given badges are enabled', () => { + beforeEach(() => { + mocks.$env = { + BADGES_ENABLED: true, + } + wrapper = Wrapper() + }) it('renders', () => { + expect(wrapper.element).toMatchSnapshot() + }) + }) + + describe('given badges are disabled', () => { + beforeEach(() => { + mocks.$env = { + BADGES_ENABLED: false, + } wrapper = Wrapper() - expect(wrapper.element.tagName).toBe('DIV') }) - describe('search', () => { - let searchAction - beforeEach(() => { - searchAction = (wrapper, { query }) => { - wrapper.find('input').setValue(query) - wrapper.find('form').trigger('submit') - return wrapper - } + it('renders', () => { + expect(wrapper.element).toMatchSnapshot() + }) + }) + + describe('search', () => { + let searchAction + beforeEach(() => { + wrapper = Wrapper() + searchAction = (wrapper, { query }) => { + wrapper.find('input').setValue(query) + wrapper.find('form').trigger('submit') + return wrapper + } + }) + + describe('query looks like an email address', () => { + it('searches users for exact email address', async () => { + const wrapper = await searchAction(Wrapper(), { query: 'email@example.org' }) + expect(wrapper.vm.email).toEqual('email@example.org') + expect(wrapper.vm.filter).toBe(null) }) - describe('query looks like an email address', () => { - it('searches users for exact email address', async () => { - const wrapper = await searchAction(Wrapper(), { query: 'email@example.org' }) - expect(wrapper.vm.email).toEqual('email@example.org') - expect(wrapper.vm.filter).toBe(null) - }) - - it('email address is case-insensitive', async () => { - const wrapper = await searchAction(Wrapper(), { query: 'eMaiL@example.org' }) - expect(wrapper.vm.email).toEqual('email@example.org') - expect(wrapper.vm.filter).toBe(null) - }) - }) - - describe('query is just text', () => { - it('tries to find matching users by `name`, `slug` or `about`', async () => { - const wrapper = await searchAction(await Wrapper(), { query: 'Find me' }) - const expected = { - OR: [ - { name_contains: 'Find me' }, - { slug_contains: 'Find me' }, - { about_contains: 'Find me' }, - ], - } - expect(wrapper.vm.email).toBe(null) - expect(wrapper.vm.filter).toEqual(expected) - }) + it('email address is case-insensitive', async () => { + const wrapper = await searchAction(Wrapper(), { query: 'eMaiL@example.org' }) + expect(wrapper.vm.email).toEqual('email@example.org') + expect(wrapper.vm.filter).toBe(null) }) }) - describe('change roles', () => { - beforeAll(() => { - wrapper = Wrapper() - wrapper.setData({ - User: [ - { - id: 'admin', - email: 'admin@example.org', - name: 'Admin', - role: 'admin', - slug: 'admin', - }, - { - id: 'user', - email: 'user@example.org', - name: 'User', - role: 'user', - slug: 'user', - }, + describe('query is just text', () => { + it('tries to find matching users by `name`, `slug` or `about`', async () => { + const wrapper = await searchAction(await Wrapper(), { query: 'Find me' }) + const expected = { + OR: [ + { name_contains: 'Find me' }, + { slug_contains: 'Find me' }, + { about_contains: 'Find me' }, ], - userRoles: ['user', 'moderator', 'admin'], - }) - }) - - it('cannot change own role', () => { - const adminRow = wrapper.findAll('tr').at(1) - expect(adminRow.find('select').exists()).toBe(false) - }) - - it('changes the role of another user', () => { - const userRow = wrapper.findAll('tr').at(2) - userRow.findAll('option').at(1).setSelected() - expect(mocks.$apollo.mutate).toHaveBeenCalledWith( - expect.objectContaining({ - variables: { - id: 'user', - role: 'moderator', - }, - }), - ) - }) - - it('toasts a success message after role has changed', () => { - const userRow = wrapper.findAll('tr').at(2) - userRow.findAll('option').at(1).setSelected() - expect(mocks.$toast.success).toHaveBeenCalled() + } + expect(wrapper.vm.email).toBe(null) + expect(wrapper.vm.filter).toEqual(expected) }) }) }) + + describe('change roles', () => { + beforeAll(() => { + wrapper = Wrapper() + wrapper.setData({ + User: [ + { + id: 'admin', + email: 'admin@example.org', + name: 'Admin', + role: 'admin', + slug: 'admin', + }, + { + id: 'user', + email: 'user@example.org', + name: 'User', + role: 'user', + slug: 'user', + }, + ], + userRoles: ['user', 'moderator', 'admin'], + }) + }) + + it('cannot change own role', () => { + const adminRow = wrapper.findAll('tr').at(1) + expect(adminRow.find('select').exists()).toBe(false) + }) + + it('changes the role of another user', () => { + const userRow = wrapper.findAll('tr').at(2) + userRow.findAll('option').at(1).setSelected() + expect(mocks.$apollo.mutate).toHaveBeenCalledWith( + expect.objectContaining({ + variables: { + id: 'user', + role: 'moderator', + }, + }), + ) + }) + + it('toasts a success message after role has changed', () => { + const userRow = wrapper.findAll('tr').at(2) + userRow.findAll('option').at(1).setSelected() + expect(mocks.$toast.success).toHaveBeenCalled() + }) + }) }) diff --git a/webapp/pages/admin/users/index.vue b/webapp/pages/admin/users/index.vue index 24258a57f..fd08f1e0c 100644 --- a/webapp/pages/admin/users/index.vue +++ b/webapp/pages/admin/users/index.vue @@ -120,7 +120,7 @@ export default { currentUser: 'auth/user', }), fields() { - return { + const fields = { index: this.$t('admin.users.table.columns.number'), name: this.$t('admin.users.table.columns.name'), email: this.$t('admin.users.table.columns.email'), @@ -142,11 +142,16 @@ export default { label: this.$t('admin.users.table.columns.role'), align: 'right', }, - badges: { + } + + if (this.$env.BADGES_ENABLED) { + fields.badges = { label: this.$t('admin.users.table.columns.badges'), align: 'right', - }, + } } + + return fields }, }, apollo: { diff --git a/webapp/pages/profile/_id/__snapshots__/_slug.spec.js.snap b/webapp/pages/profile/_id/__snapshots__/_slug.spec.js.snap new file mode 100644 index 000000000..9eec4e96a --- /dev/null +++ b/webapp/pages/profile/_id/__snapshots__/_slug.spec.js.snap @@ -0,0 +1,2442 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ProfileSlug given an authenticated user given another profile user and badges are disabled renders 1`] = ` +
+
+
+ +
+
+
+
+ + BTB + + + + + +
+ + + + + +
+
+ +
+
+
+
+ +
+

+ + Bob the builder + +

+ +

+ + @undefined + +

+ + + +

+ + profile.memberSince + +

+
+ + + +
+
+ +
+

+ + + + + 0 + + + + +

+

+ + profile.followers + +

+
+
+
+ +
+ +
+

+ + + + + 0 + + + + +

+

+ + profile.following + +

+
+
+
+
+ +
+ + + + + + + +
+ + + + +
+ +
+ +

+ + profile.network.title + +

+ + + +
+ + + + +
+ +
+ + + +
+ + + + + + + +
+
+
+
+
+
+`; + +exports[`ProfileSlug given an authenticated user given another profile user and badges are enabled renders 1`] = ` +
+
+
+ +
+
+
+
+ + BTB + + + + + +
+ + + + + +
+
+ +
+
+
+
+ +
+

+ + Bob the builder + +

+ +

+ + @undefined + +

+ + + +

+ + profile.memberSince + +

+
+ +
+ + +
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+ +
+

+ + + + + 0 + + + + +

+

+ + profile.followers + +

+
+
+
+ +
+ +
+

+ + + + + 0 + + + + +

+

+ + profile.following + +

+
+
+
+
+ +
+ + + + + + + +
+ + + + +
+ +
+ +

+ + profile.network.title + +

+ + + +
+ + + + +
+ +
+ + + +
+ + + + + + + +
+
+
+
+
+
+`; + +exports[`ProfileSlug given an authenticated user given the logged in user as profile user and badges are disabled renders 1`] = ` +
+
+
+ +
+
+
+
+
+
+
+
+ + BTB + + + + + +
+ +
+
+ +
+
+
+
+
+
+ + + + + +
+
+ +
+
+
+
+ +
+

+ + Bob the builder + +

+ +

+ + @undefined + +

+ + + +

+ + profile.memberSince + +

+
+ + + +
+
+ +
+

+ + + + + 0 + + + + +

+

+ + profile.followers + +

+
+
+
+ +
+ +
+

+ + + + + 0 + + + + +

+

+ + profile.following + +

+
+
+
+
+ + + + + + +
+ +
+ +

+ + profile.network.title + +

+ + + +
+ + + + +
+ +
+ + + +
+ + + + + + + +
+
+
+
+
+
+`; + +exports[`ProfileSlug given an authenticated user given the logged in user as profile user and badges are enabled renders 1`] = ` +
+
+
+ +
+
+
+
+
+
+
+
+ + BTB + + + + + +
+ +
+
+ +
+
+
+
+
+
+ + + + + +
+
+ +
+
+
+
+ +
+

+ + Bob the builder + +

+ +

+ + @undefined + +

+ + + +

+ + profile.memberSince + +

+
+ + + +
+
+ +
+

+ + + + + 0 + + + + +

+

+ + profile.followers + +

+
+
+
+ +
+ +
+

+ + + + + 0 + + + + +

+

+ + profile.following + +

+
+
+
+
+ + + + + + +
+ +
+ +

+ + profile.network.title + +

+ + + +
+ + + + +
+ +
+ + + +
+ + + + + + + +
+
+
+
+
+
+`; diff --git a/webapp/pages/profile/_id/_slug.spec.js b/webapp/pages/profile/_id/_slug.spec.js index 5ab87ad3a..a4cc473c3 100644 --- a/webapp/pages/profile/_id/_slug.spec.js +++ b/webapp/pages/profile/_id/_slug.spec.js @@ -1,22 +1,25 @@ -import { mount } from '@vue/test-utils' +import { render } from '@testing-library/vue' import ProfileSlug from './_slug.vue' const localVue = global.localVue localVue.filter('date', (d) => d) +// Mock Math.random, used in Dropdown +Object.assign(Math, { + random: () => 0, +}) + const stubs = { 'client-only': true, 'v-popover': true, 'nuxt-link': true, - 'infinite-loading': true, 'follow-list': true, 'router-link': true, } describe('ProfileSlug', () => { let wrapper - let Wrapper let mocks beforeEach(() => { @@ -25,7 +28,7 @@ describe('ProfileSlug', () => { id: 'p23', name: 'It is a post', }, - $t: jest.fn(), + $t: jest.fn((t) => t), // If you're mocking router, then don't use VueRouter with localVue: https://vue-test-utils.vuejs.org/guides/using-with-vue-router.html $route: { params: { @@ -49,49 +52,144 @@ describe('ProfileSlug', () => { } }) - describe('mount', () => { - Wrapper = () => { - return mount(ProfileSlug, { - mocks, - localVue, - stubs, - }) - } + const Wrapper = (badgesEnabled, data) => { + return render(ProfileSlug, { + localVue, + stubs, + data: () => data, + mocks: { + ...mocks, + $env: { + BADGES_ENABLED: badgesEnabled, + }, + }, + }) + } - describe('given an authenticated user', () => { - beforeEach(() => { - mocks.$filters = { - removeLinks: (c) => c, - truncate: (a) => a, - } - mocks.$store = { - getters: { - 'auth/isModerator': () => false, - 'auth/user': { - id: 'u23', - }, + describe('given an authenticated user', () => { + beforeEach(() => { + mocks.$filters = { + removeLinks: (c) => c, + truncate: (a) => a, + } + mocks.$store = { + getters: { + 'auth/isModerator': () => false, + 'auth/user': { + id: 'u23', }, - } - }) + }, + } + }) - describe('given a user for the profile', () => { - beforeEach(() => { - wrapper = Wrapper() - wrapper.setData({ - User: [ + describe('given another profile user', () => { + const user = { + User: [ + { + id: 'u3', + name: 'Bob the builder', + contributionsCount: 6, + shoutedCount: 7, + commentedCount: 8, + badgeVerification: { + id: 'bv1', + icon: '/path/to/icon-bv1', + description: 'verified', + isDefault: false, + }, + badgeTrophiesSelected: [ { - id: 'u3', - name: 'Bob the builder', - contributionsCount: 6, - shoutedCount: 7, - commentedCount: 8, + id: 'bt1', + icon: '/path/to/icon-bt1', + description: 'a trophy', + isDefault: false, + }, + { + id: 'bt2', + icon: '/path/to/icon-bt2', + description: 'no trophy', + isDefault: true, }, ], - }) + }, + ], + } + + describe('and badges are enabled', () => { + beforeEach(() => { + wrapper = Wrapper(true, user) }) - it('displays name of the user', () => { - expect(wrapper.text()).toContain('Bob the builder') + it('renders', () => { + expect(wrapper.container).toMatchSnapshot() + }) + }) + + describe('and badges are disabled', () => { + beforeEach(() => { + wrapper = Wrapper(false, user) + }) + + it('renders', () => { + expect(wrapper.container).toMatchSnapshot() + }) + }) + }) + + describe('given the logged in user as profile user', () => { + beforeEach(() => { + mocks.$route.params.id = 'u23' + }) + + const user = { + User: [ + { + id: 'u23', + name: 'Bob the builder', + contributionsCount: 6, + shoutedCount: 7, + commentedCount: 8, + badgeVerification: { + id: 'bv1', + icon: '/path/to/icon-bv1', + description: 'verified', + isDefault: false, + }, + badgeTrophiesSelected: [ + { + id: 'bt1', + icon: '/path/to/icon-bt1', + description: 'a trophy', + isDefault: false, + }, + { + id: 'bt2', + icon: '/path/to/icon-bt2', + description: 'no trophy', + isDefault: true, + }, + ], + }, + ], + } + + describe('and badges are enabled', () => { + beforeEach(() => { + wrapper = Wrapper(true, user) + }) + + it('renders', () => { + expect(wrapper.container).toMatchSnapshot() + }) + }) + + describe('and badges are disabled', () => { + beforeEach(() => { + wrapper = Wrapper(false, user) + }) + + it('renders', () => { + expect(wrapper.container).toMatchSnapshot() }) }) }) diff --git a/webapp/pages/profile/_id/_slug.vue b/webapp/pages/profile/_id/_slug.vue index 382350faf..38035e217 100644 --- a/webapp/pages/profile/_id/_slug.vue +++ b/webapp/pages/profile/_id/_slug.vue @@ -42,8 +42,11 @@ {{ $t('profile.memberSince') }} {{ user.createdAt | date('MMMM yyyy') }} - - + + + + + @@ -266,6 +269,10 @@ export default { user() { return this.User ? this.User[0] : {} }, + userBadges() { + if (!this.$env.BADGES_ENABLED) return null + return [this.user.badgeVerification, ...(this.user.badgeTrophiesSelected || [])] + }, userName() { const { name } = this.user || {} return name || this.$t('profile.userAnonym') @@ -456,6 +463,12 @@ export default { margin: auto; margin-top: -60px; } +.badge-edit-link { + transition: all 0.2s ease-out; + &:hover { + opacity: 0.7; + } +} .page-name-profile-id-slug { .ds-flex-item:first-child .content-menu { position: absolute; diff --git a/webapp/pages/settings.spec.js b/webapp/pages/settings.spec.js index 0f3c6e22c..8c2917c90 100644 --- a/webapp/pages/settings.spec.js +++ b/webapp/pages/settings.spec.js @@ -1,4 +1,4 @@ -import { mount } from '@vue/test-utils' +import { render } from '@testing-library/vue' import settings from './settings.vue' const localVue = global.localVue @@ -17,21 +17,37 @@ describe('settings.vue', () => { } }) - describe('mount', () => { - const Wrapper = () => { - return mount(settings, { - mocks, - localVue, - stubs, - }) - } + const Wrapper = () => { + return render(settings, { + mocks, + localVue, + stubs, + }) + } + describe('given badges are enabled', () => { beforeEach(() => { + mocks.$env = { + BADGES_ENABLED: true, + } wrapper = Wrapper() }) it('renders', () => { - expect(wrapper.element.tagName).toBe('DIV') + expect(wrapper.container).toMatchSnapshot() + }) + }) + + describe('given badges are disabled', () => { + beforeEach(() => { + mocks.$env = { + BADGES_ENABLED: false, + } + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.container).toMatchSnapshot() }) }) }) diff --git a/webapp/pages/settings.vue b/webapp/pages/settings.vue index 5d526c3cc..e1181650e 100644 --- a/webapp/pages/settings.vue +++ b/webapp/pages/settings.vue @@ -21,7 +21,7 @@ export default { computed: { routes() { - return [ + const routes = [ { name: this.$t('settings.data.name'), path: `/settings`, @@ -83,6 +83,15 @@ export default { }, } */ ] + + if (this.$env.BADGES_ENABLED) { + routes.splice(2, 0, { + name: this.$t('settings.badges.name'), + path: `/settings/badges`, + }) + } + + return routes }, }, } diff --git a/webapp/pages/settings/__snapshots__/badges.spec.js.snap b/webapp/pages/settings/__snapshots__/badges.spec.js.snap new file mode 100644 index 000000000..358327202 --- /dev/null +++ b/webapp/pages/settings/__snapshots__/badges.spec.js.snap @@ -0,0 +1,429 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`badge settings with badges more badges available selecting an empty slot shows list with available badges 1`] = ` +
+
+

+ settings.badges.name +

+ +

+ settings.badges.description +

+ +
+
+
+ + + + +
+
+ + + +
+ + + settings.badges.click-to-use + + +
+ + + +
+
+ + +
+
+
+ + +
+
+`; + +exports[`badge settings with badges no more badges available selecting an empty slot shows no more badges available message 1`] = ` +
+
+

+ settings.badges.name +

+ +

+ settings.badges.description +

+ +
+
+
+ + + + +
+
+ +

+ + settings.badges.no-badges-available + +

+ + + + + + +
+ + +
+
+`; + +exports[`badge settings with badges renders 1`] = ` +
+
+

+ settings.badges.name +

+ +

+ settings.badges.description +

+ +
+
+
+ + + + +
+
+ + + +
+ + + settings.badges.click-to-select + + +
+ + + + +
+ + +
+
+`; + +exports[`badge settings with badges selecting a used badge clicking remove badge button with successful server request removes the badge 1`] = ` +
+
+

+ settings.badges.name +

+ +

+ settings.badges.description +

+ +
+
+
+ + + + +
+
+ + + + + + +
+ + +
+
+`; + +exports[`badge settings without badges renders 1`] = ` +
+
+

+ settings.badges.name +

+ +

+ settings.badges.description +

+ +
+
+
+ +
+
+ + + + + + + + +
+ + +
+
+`; diff --git a/webapp/pages/settings/badges.spec.js b/webapp/pages/settings/badges.spec.js new file mode 100644 index 000000000..291fd75d6 --- /dev/null +++ b/webapp/pages/settings/badges.spec.js @@ -0,0 +1,302 @@ +import { render, screen, fireEvent } from '@testing-library/vue' +import '@testing-library/jest-dom' +import badges from './badges.vue' + +const localVue = global.localVue + +describe('badge settings', () => { + let mocks + + const apolloMutateMock = jest.fn() + + const Wrapper = () => { + return render(badges, { + localVue, + mocks, + }) + } + + beforeEach(() => { + mocks = { + $t: jest.fn((t) => t), + $toast: { + success: jest.fn(), + error: jest.fn(), + }, + $apollo: { + mutate: apolloMutateMock, + }, + } + }) + + describe('without badges', () => { + beforeEach(() => { + mocks.$store = { + getters: { + 'auth/isModerator': () => false, + 'auth/user': { + id: 'u23', + badgeVerification: { + id: 'bv1', + icon: '/verification/icon', + description: 'Verification description', + isDefault: true, + }, + badgeTrophiesSelected: [], + badgeTrophiesUnused: [], + }, + }, + } + }) + + it('renders', () => { + const wrapper = Wrapper() + expect(wrapper.container).toMatchSnapshot() + }) + }) + + describe('with badges', () => { + const badgeTrophiesSelected = [ + { + id: '1', + icon: '/path/to/some/icon', + isDefault: false, + description: 'Some description', + }, + { + id: '2', + icon: '/path/to/empty/icon', + isDefault: true, + description: 'Empty', + }, + { + id: '3', + icon: '/path/to/third/icon', + isDefault: false, + description: 'Third description', + }, + ] + + const badgeTrophiesUnused = [ + { + id: '4', + icon: '/path/to/fourth/icon', + description: 'Fourth description', + }, + { + id: '5', + icon: '/path/to/fifth/icon', + description: 'Fifth description', + }, + ] + + let wrapper + + beforeEach(() => { + mocks.$store = { + getters: { + 'auth/isModerator': () => false, + 'auth/user': { + id: 'u23', + badgeVerification: { + id: 'bv1', + icon: '/verification/icon', + description: 'Verification description', + isDefault: false, + }, + badgeTrophiesSelected, + badgeTrophiesUnused, + }, + }, + } + wrapper = Wrapper() + }) + + it('renders', () => { + expect(wrapper.container).toMatchSnapshot() + }) + + describe('selecting a used badge', () => { + beforeEach(async () => { + const badge = screen.getByTitle(badgeTrophiesSelected[0].description) + await fireEvent.click(badge) + }) + + it('shows remove badge button', () => { + expect(screen.getByText('settings.badges.remove')).toBeInTheDocument() + }) + + describe('clicking remove badge button', () => { + const clickButton = async () => { + const removeButton = screen.getByText('settings.badges.remove') + await fireEvent.click(removeButton) + } + + describe('with successful server request', () => { + beforeEach(() => { + apolloMutateMock.mockResolvedValue({ + data: { + setTrophyBadgeSelected: { + id: 'u23', + badgeTrophiesSelected: [ + { + id: '2', + icon: '/path/to/empty/icon', + isDefault: true, + description: 'Empty', + }, + { + id: '2', + icon: '/path/to/empty/icon', + isDefault: true, + description: 'Empty', + }, + { + id: '3', + icon: '/path/to/third/icon', + isDefault: false, + description: 'Third description', + }, + ], + }, + }, + }) + clickButton() + }) + + it('calls the server', () => { + expect(apolloMutateMock).toHaveBeenCalledWith({ + mutation: expect.anything(), + update: expect.anything(), + variables: { + badgeId: null, + slot: 0, + }, + }) + }) + + /* To test this, we would need a better apollo mock */ + it.skip('removes the badge', async () => { + expect(wrapper.container).toMatchSnapshot() + }) + + it('shows a success message', () => { + expect(mocks.$toast.success).toHaveBeenCalledWith('settings.badges.success-update') + }) + }) + + describe('with failed server request', () => { + beforeEach(() => { + apolloMutateMock.mockRejectedValue({ message: 'Ouch!' }) + clickButton() + }) + + it('shows an error message', () => { + expect(mocks.$toast.error).toHaveBeenCalledWith('settings.badges.error-update') + }) + }) + }) + }) + + describe('no more badges available', () => { + beforeEach(async () => { + mocks.$store.getters['auth/user'].badgeTrophiesUnused = [] + }) + + describe('selecting an empty slot', () => { + beforeEach(async () => { + const emptySlot = screen.getAllByTitle('Empty')[0] + await fireEvent.click(emptySlot) + }) + + it('shows no more badges available message', () => { + expect(wrapper.container).toMatchSnapshot() + }) + }) + }) + + describe('more badges available', () => { + describe('selecting an empty slot', () => { + beforeEach(async () => { + const emptySlot = screen.getAllByTitle('Empty')[0] + await fireEvent.click(emptySlot) + }) + + it('shows list with available badges', () => { + expect(wrapper.container).toMatchSnapshot() + }) + + describe('clicking on an available badge', () => { + const clickBadge = async () => { + const badge = screen.getByText(badgeTrophiesUnused[0].description) + await fireEvent.click(badge) + } + + describe('with successful server request', () => { + beforeEach(() => { + apolloMutateMock.mockResolvedValue({ + data: { + setTrophyBadgeSelected: { + id: 'u23', + badgeTrophiesSelected: [ + { + id: '4', + icon: '/path/to/fourth/icon', + description: 'Fourth description', + isDefault: false, + }, + { + id: '2', + icon: '/path/to/empty/icon', + isDefault: true, + description: 'Empty', + }, + { + id: '3', + icon: '/path/to/third/icon', + isDefault: false, + description: 'Third description', + }, + ], + }, + }, + }) + clickBadge() + }) + + it('calls the server', () => { + expect(apolloMutateMock).toHaveBeenCalledWith({ + mutation: expect.anything(), + update: expect.anything(), + variables: { + badgeId: '4', + slot: 1, + }, + }) + }) + + /* To test this, we would need a better apollo mock */ + it.skip('adds the badge', async () => { + expect(wrapper.container).toMatchSnapshot() + }) + + it('shows a success message', () => { + expect(mocks.$toast.success).toHaveBeenCalledWith('settings.badges.success-update') + }) + }) + + describe('with failed server request', () => { + beforeEach(() => { + apolloMutateMock.mockRejectedValue({ message: 'Ouch!' }) + clickBadge() + }) + + it('shows an error message', () => { + expect(mocks.$toast.error).toHaveBeenCalledWith('settings.badges.error-update') + }) + }) + }) + }) + }) + }) +}) diff --git a/webapp/pages/settings/badges.vue b/webapp/pages/settings/badges.vue new file mode 100644 index 000000000..3f0e7c7e7 --- /dev/null +++ b/webapp/pages/settings/badges.vue @@ -0,0 +1,176 @@ + + + + + diff --git a/webapp/static/img/badges/stars.svg b/webapp/static/img/badges/stars.svg new file mode 100644 index 000000000..44d64a5f4 --- /dev/null +++ b/webapp/static/img/badges/stars.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 1ef19363e..e17834008 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -7,6 +7,11 @@ resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== +"@adobe/css-tools@^4.4.0": + version "4.4.2" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.2.tgz#c836b1bd81e6d62cd6cdf3ee4948bcdce8ea79c8" + integrity sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A== + "@ampproject/remapping@^2.2.0": version "2.2.1" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" @@ -4253,6 +4258,19 @@ lz-string "^1.5.0" pretty-format "^27.0.2" +"@testing-library/jest-dom@^6.6.3": + version "6.6.3" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz#26ba906cf928c0f8172e182c6fe214eb4f9f2bd2" + integrity sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA== + dependencies: + "@adobe/css-tools" "^4.4.0" + aria-query "^5.0.0" + chalk "^3.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.6.3" + lodash "^4.17.21" + redent "^3.0.0" + "@testing-library/vue@5": version "5.9.0" resolved "https://registry.yarnpkg.com/@testing-library/vue/-/vue-5.9.0.tgz#d33c52ae89e076808abe622f70dcbccb1b5d080c" @@ -6058,6 +6076,11 @@ aria-query@5.1.3: dependencies: deep-equal "^2.0.5" +aria-query@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59" + integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw== + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -8230,6 +8253,11 @@ css-what@2.1, css-what@^2.1.2: resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + csscolorparser@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/csscolorparser/-/csscolorparser-1.0.3.tgz#b34f391eea4da8f3e98231e2ccd8df9c041f171b" @@ -8793,6 +8821,11 @@ dom-accessibility-api@^0.5.9: resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== +dom-accessibility-api@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8" + integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== + dom-converter@^0.2: version "0.2.0" resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" From 2f808f8fcc01ef2e547d20e54cc37951734660c0 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 25 Apr 2025 19:45:56 +0200 Subject: [PATCH 39/54] revokeBadge also removes selection (#8437) --- backend/src/schema/resolvers/badges.spec.ts | 220 +++++++++++++++++++- backend/src/schema/resolvers/badges.ts | 6 +- 2 files changed, 221 insertions(+), 5 deletions(-) diff --git a/backend/src/schema/resolvers/badges.spec.ts b/backend/src/schema/resolvers/badges.spec.ts index ae2fe0b0d..e6b5173a9 100644 --- a/backend/src/schema/resolvers/badges.spec.ts +++ b/backend/src/schema/resolvers/badges.spec.ts @@ -524,25 +524,30 @@ describe('Badges', () => { beforeEach(async () => { await regularUser.relateTo(badge, 'rewarded') await regularUser.relateTo(verification, 'verifies') + await regularUser.relateTo(badge, 'selected', { slot: 6 }) }) const revokeBadgeMutation = gql` mutation ($badgeId: ID!, $userId: ID!) { revokeBadge(badgeId: $badgeId, userId: $userId) { id + badgeTrophies { + id + } badgeVerification { id isDefault } - badgeTrophies { + badgeTrophiesSelected { id + isDefault } } } ` describe('check test setup', () => { - it('user has one badge', async () => { + it('user has one badge and has it selected', async () => { authenticatedUser = regularUser.toJson() const userQuery = gql` { @@ -551,11 +556,68 @@ describe('Badges', () => { badgeTrophies { id } + badgeVerification { + id + isDefault + } + badgeTrophiesSelected { + id + isDefault + } } } ` const expected = { - data: { User: [{ badgeTrophiesCount: 1, badgeTrophies: [{ id: 'trophy_rhino' }] }] }, + data: { + User: [ + { + badgeTrophiesCount: 1, + badgeTrophies: [{ id: 'trophy_rhino' }], + badgeVerification: { + id: 'verification_moderator', + isDefault: false, + }, + badgeTrophiesSelected: [ + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'trophy_rhino', + isDefault: false, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + ], + }, + ], + }, errors: undefined, } await expect(query({ query: userQuery })).resolves.toMatchObject(expected) @@ -601,6 +663,44 @@ describe('Badges', () => { id: 'regular-user-id', badgeVerification: { id: 'verification_moderator', isDefault: false }, badgeTrophies: [], + badgeTrophiesSelected: [ + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + ], }, }, errors: undefined, @@ -615,6 +715,44 @@ describe('Badges', () => { id: 'regular-user-id', badgeVerification: { id: 'verification_moderator', isDefault: false }, badgeTrophies: [], + badgeTrophiesSelected: [ + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + ], }, }, errors: undefined, @@ -636,6 +774,44 @@ describe('Badges', () => { id: 'regular-user-id', badgeVerification: { id: 'default_verification', isDefault: true }, badgeTrophies: [{ id: 'trophy_rhino' }], + badgeTrophiesSelected: [ + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'trophy_rhino', + isDefault: false, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + ], }, }, errors: undefined, @@ -664,6 +840,44 @@ describe('Badges', () => { id: 'regular-user-id', badgeVerification: { id: 'default_verification', isDefault: true }, badgeTrophies: [{ id: 'trophy_rhino' }], + badgeTrophiesSelected: [ + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'trophy_rhino', + isDefault: false, + }, + { + id: 'default_trophy', + isDefault: true, + }, + { + id: 'default_trophy', + isDefault: true, + }, + ], }, }, errors: undefined, diff --git a/backend/src/schema/resolvers/badges.ts b/backend/src/schema/resolvers/badges.ts index 587204b54..7c107e42c 100644 --- a/backend/src/schema/resolvers/badges.ts +++ b/backend/src/schema/resolvers/badges.ts @@ -119,8 +119,10 @@ export default { const response = await transaction.run( ` MATCH (user:User {id: $userId}) - OPTIONAL MATCH (badge:Badge {id: $badgeId})-[relation:REWARDED|VERIFIES]->(user) - DELETE relation + OPTIONAL MATCH (badge:Badge {id: $badgeId})-[rewarded:REWARDED|VERIFIES]->(user) + OPTIONAL MATCH (user)-[selected:SELECTED]->(badge) + DELETE rewarded + DELETE selected RETURN user {.*} `, { From 7482f8665e7da76a68ec0d34c7f39209a9179ca6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 26 Apr 2025 01:35:02 +0200 Subject: [PATCH 40/54] build(deps-dev): bump eslint-config-prettier in /backend (#8370) Bumps [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) from 10.1.1 to 10.1.2. - [Release notes](https://github.com/prettier/eslint-config-prettier/releases) - [Changelog](https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/eslint-config-prettier/compare/v10.1.1...v10.1.2) --- updated-dependencies: - dependency-name: eslint-config-prettier dependency-version: 10.1.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- backend/package.json | 2 +- backend/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/package.json b/backend/package.json index d904bbd20..baf0e13ce 100644 --- a/backend/package.json +++ b/backend/package.json @@ -110,7 +110,7 @@ "@typescript-eslint/parser": "^5.62.0", "apollo-server-testing": "~2.11.0", "eslint": "^8.57.1", - "eslint-config-prettier": "^10.1.1", + "eslint-config-prettier": "^10.1.2", "eslint-config-standard": "^17.1.0", "eslint-import-resolver-typescript": "^4.3.2", "eslint-plugin-import": "^2.31.0", diff --git a/backend/yarn.lock b/backend/yarn.lock index c972f9925..46c6bb98d 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -4586,10 +4586,10 @@ eslint-compat-utils@^0.5.1: dependencies: semver "^7.5.4" -eslint-config-prettier@^10.1.1: - version "10.1.1" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.1.1.tgz#cf0ff6e5c4e7e15f129f1f1ce2a5ecba92dec132" - integrity sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw== +eslint-config-prettier@^10.1.2: + version "10.1.2" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.1.2.tgz#31a4b393c40c4180202c27e829af43323bf85276" + integrity sha512-Epgp/EofAUeEpIdZkW60MHKvPyru1ruQJxPL+WIycnaPApuseK0Zpkrh/FwL9oIpQvIhJwV7ptOy0DWUjTlCiA== eslint-config-standard@^17.1.0: version "17.1.0" From 91bdf3ca1ec044108ec78105bf71cac3a8c915a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 26 Apr 2025 12:52:14 +0200 Subject: [PATCH 41/54] build(deps-dev): bump eslint-import-resolver-typescript in /backend (#8445) Bumps [eslint-import-resolver-typescript](https://github.com/import-js/eslint-import-resolver-typescript) from 4.3.2 to 4.3.4. - [Release notes](https://github.com/import-js/eslint-import-resolver-typescript/releases) - [Changelog](https://github.com/import-js/eslint-import-resolver-typescript/blob/master/CHANGELOG.md) - [Commits](https://github.com/import-js/eslint-import-resolver-typescript/compare/v4.3.2...v4.3.4) --- updated-dependencies: - dependency-name: eslint-import-resolver-typescript dependency-version: 4.3.4 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- backend/package.json | 2 +- backend/yarn.lock | 221 +++++++++++++++++++++++-------------------- 2 files changed, 118 insertions(+), 105 deletions(-) diff --git a/backend/package.json b/backend/package.json index baf0e13ce..39a0016bc 100644 --- a/backend/package.json +++ b/backend/package.json @@ -112,7 +112,7 @@ "eslint": "^8.57.1", "eslint-config-prettier": "^10.1.2", "eslint-config-standard": "^17.1.0", - "eslint-import-resolver-typescript": "^4.3.2", + "eslint-import-resolver-typescript": "^4.3.4", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jest": "^28.11.0", "eslint-plugin-n": "^17.17.0", diff --git a/backend/yarn.lock b/backend/yarn.lock index 46c6bb98d..f37877286 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -1579,10 +1579,10 @@ url-regex "~4.1.1" video-extensions "~1.1.0" -"@napi-rs/wasm-runtime@^0.2.8": - version "0.2.8" - resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.8.tgz#642e8390ee78ed21d6b79c467aa610e249224ed6" - integrity sha512-OBlgKdX7gin7OIq4fadsjpg+cp2ZphvAIKucHsNfTdJiqdOmOEwQd/bHi0VwNrcw5xpBJyUw6cK/QilCqy1BSg== +"@napi-rs/wasm-runtime@^0.2.9": + version "0.2.9" + resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.9.tgz#7278122cf94f3b36d8170a8eee7d85356dfa6a96" + integrity sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg== dependencies: "@emnapi/core" "^1.4.0" "@emnapi/runtime" "^1.4.0" @@ -2294,87 +2294,92 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== -"@unrs/resolver-binding-darwin-arm64@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.5.0.tgz#0c64ebe422a3d05ada91d8ba84e037383742c955" - integrity sha512-YmocNlEcX/AgJv8gI41bhjMOTcKcea4D2nRIbZj+MhRtSH5+vEU8r/pFuTuoF+JjVplLsBueU+CILfBPVISyGQ== +"@unrs/resolver-binding-darwin-arm64@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.7.0.tgz#04fe2253f2b6366ae993b1565c6495e563ad8a4c" + integrity sha512-vIWAU56r2lZAmUsljp6m9+hrTlwNkZH6pqnSPff2WxzofV+jWRSHLmZRUS+g+VE+LlyPByifmGGHpJmhWetatg== -"@unrs/resolver-binding-darwin-x64@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.5.0.tgz#57210874eca22ec3a07039c97c028fb19c0c6d57" - integrity sha512-qpUrXgH4e/0xu1LOhPEdfgSY3vIXOxDQv370NEL8npN8h40HcQDA+Pl2r4HBW6tTXezWIjxUFcP7tj529RZtDw== +"@unrs/resolver-binding-darwin-x64@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.7.0.tgz#8d74ee589f1c379b9b75880ea85871bdaf89766e" + integrity sha512-+bShFLgtdwuNteQbKq3X230754AouNMXSLDZ56EssgDyckDt6Ld7wRaJjZF0pY671HnY2pk9/amO4amAFzfN1A== -"@unrs/resolver-binding-freebsd-x64@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.5.0.tgz#4519371d0ad8e557a86623d8497e3abcdcb5ae43" - integrity sha512-3tX8r8vgjvZzaJZB4jvxUaaFCDCb3aWDCpZN3EjhGnnwhztslI05KSG5NY/jNjlcZ5QWZ7dEZZ/rNBFsmTaSPw== +"@unrs/resolver-binding-freebsd-x64@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.7.0.tgz#d0bcea8e240d54d048aa45a6c7bd7e4d4824abfb" + integrity sha512-HJjXb3aIptDZQ0saSmk2S4W1pWNVZ2iNpAbNGZOfsUXbi8xwCmHdVjErNS92hRp7djuDLup1OLrzOMtTdw5BmA== -"@unrs/resolver-binding-linux-arm-gnueabihf@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.5.0.tgz#4fc05aec9e65a6478003a0b9034a06ac0da886ab" - integrity sha512-FH+ixzBKaUU9fWOj3TYO+Yn/eO6kYvMLV9eNJlJlkU7OgrxkCmiMS6wUbyT0KA3FOZGxnEQ2z3/BHgYm2jqeLA== +"@unrs/resolver-binding-linux-arm-gnueabihf@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.7.0.tgz#ae56292948a47a876d894da740b8001a14c88bc3" + integrity sha512-NF3lk7KHulLD97UE+MHjH0mrOjeZG8Hz10h48YcFz2V0rlxBdRSRcMbGer8iH/1mIlLqxtvXJfGLUr4SMj0XZg== -"@unrs/resolver-binding-linux-arm-musleabihf@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.5.0.tgz#c24b35dd5818fcd25569425b1dc1a98a883e248b" - integrity sha512-pxCgXMgwB/4PfqFQg73lMhmWwcC0j5L+dNXhZoz/0ek0iS/oAWl65fxZeT/OnU7fVs52MgdP2q02EipqJJXHSg== +"@unrs/resolver-binding-linux-arm-musleabihf@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.7.0.tgz#4a32424660d2f0ed328297b24f46e64f4c2990d8" + integrity sha512-Gn1c/t24irDgU8yYj4vVG6qHplwUM42ti9/zYWgfmFjoXCH6L4Ab9hh6HuO7bfDSvGDRGWQt1IVaBpgbKHdh3Q== -"@unrs/resolver-binding-linux-arm64-gnu@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.5.0.tgz#07dc8478a0a356d343790208dc557d6d053689af" - integrity sha512-FX2FV7vpLE/+Z0NZX9/1pwWud5Wocm/2PgpUXbT5aSV3QEB10kBPJAzssOQylvdj8mOHoKl5pVkXpbCwww/T2g== +"@unrs/resolver-binding-linux-arm64-gnu@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.7.0.tgz#7b9d73558a2d85911c82314784edb89dcd0b274d" + integrity sha512-XRrVXRIUP++qyqAqgiXUpOv0GP3cHx7aA7NrzVFf6Cc8FoYuwtnmT+vctfSo4wRZN71MNU4xq2BEFxI4qvSerg== -"@unrs/resolver-binding-linux-arm64-musl@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.5.0.tgz#169e531731f7e462dffa410034a1d06a7a921aa8" - integrity sha512-+gF97xst1BZb28T3nwwzEtq2ewCoMDGKsenYsZuvpmNrW0019G1iUAunZN+FG55L21y+uP7zsGX06OXDQ/viKw== +"@unrs/resolver-binding-linux-arm64-musl@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.7.0.tgz#7d7305c5f5610744ef7a373d2a9022c922113568" + integrity sha512-Sligg+vTDAYTXkUtgviPjGEFIh57pkvlfdyRw21i9gkjp/eCNOAi2o5e7qLGTkoYdJHZJs5wVMViPEmAbw2/Tg== -"@unrs/resolver-binding-linux-ppc64-gnu@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.5.0.tgz#f6ad2ff47d74c8158b28a18536a71a8ecf84a17f" - integrity sha512-5bEmVcQw9js8JYM2LkUBw5SeELSIxX+qKf9bFrfFINKAp4noZ//hUxLpbF7u/3gTBN1GsER6xOzIZlw/VTdXtA== +"@unrs/resolver-binding-linux-ppc64-gnu@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.7.0.tgz#280e4846c3bd9b81fdda25ac3cdda203da9bfd20" + integrity sha512-Apek8/x+7Rg33zUJlQV44Bvq8/t1brfulk0veNJrk9wprF89bCYFMUHF7zQYcpf2u+m1+qs3mYQrBd43fGXhMA== -"@unrs/resolver-binding-linux-riscv64-gnu@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.5.0.tgz#2f3986cb44f285f90d27e87cee8b4059de3ffbdd" - integrity sha512-GGk/8TPUsf1Q99F+lzMdjE6sGL26uJCwQ9TlvBs8zR3cLQNw/MIumPN7zrs3GFGySjnwXc8gA6J3HKbejywmqA== +"@unrs/resolver-binding-linux-riscv64-gnu@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.7.0.tgz#63301787af18d158ab4e99ec5041f507da228721" + integrity sha512-kBale8CFX5clfV9VmI9EwKw2ZACMEx1ecjV92F9SeWTUoxl9d+LGzS6zMSX3kGYqcfJB3NXMwLCTwIDBLG1y4g== -"@unrs/resolver-binding-linux-s390x-gnu@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.5.0.tgz#813ea07833012bc34ecc59f023e422b421138761" - integrity sha512-5uRkFYYVNAeVaA4W/CwugjFN3iDOHCPqsBLCCOoJiMfFMMz4evBRsg+498OFa9w6VcTn2bD5aI+RRayaIgk2Sw== +"@unrs/resolver-binding-linux-riscv64-musl@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.7.0.tgz#b85d66b2c4d73fe335d448322c708448c4487c44" + integrity sha512-s/Q33xQjeFHSCvGl1sZztFZF6xhv7coMvFz6wa/x/ZlEArjiQoMMwGa/Aieq1Kp/6+S13iU3/IJF0ga6/451ow== -"@unrs/resolver-binding-linux-x64-gnu@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.5.0.tgz#18b0d7553268fa490db92be578ac4b0fd8cae049" - integrity sha512-j905CZH3nehYy6NimNqC2B14pxn4Ltd7guKMyPTzKehbFXTUgihQS/ZfHQTdojkMzbSwBOSgq1dOrY+IpgxDsA== +"@unrs/resolver-binding-linux-s390x-gnu@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.7.0.tgz#f4202f7bebd823e0744a785ac1426f07129a2f81" + integrity sha512-7PuNXAo97ydaxVNrIYJzPipvINJafDpB8pt5CoZHfu8BmqcU6d7kl6/SABTnqNffNkd6Cfhuo70jvGB2P7oJ/Q== -"@unrs/resolver-binding-linux-x64-musl@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.5.0.tgz#04541e98d16e358c695393251e365bc3d802dfa4" - integrity sha512-dmLevQTuzQRwu5A+mvj54R5aye5I4PVKiWqGxg8tTaYP2k2oTs/3Mo8mgnhPk28VoYCi0fdFYpgzCd4AJndQvQ== +"@unrs/resolver-binding-linux-x64-gnu@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.7.0.tgz#c3fa31d0b4cc49d54c956dec43bead5a0c4127cf" + integrity sha512-fNosEzDMYItA4It+R0tioHwKlEfx/3TkkJdP2x9B5o9R946NDC4ZZj5ZjA+Y4NQD2V/imB3QPAKmeh3vHQGQyA== -"@unrs/resolver-binding-wasm32-wasi@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.5.0.tgz#7a2ae7467c4c52d53c20ad7fc2bace1b23de8168" - integrity sha512-LtJMhwu7avhoi+kKfAZOKN773RtzLBVVF90YJbB0wyMpUj9yQPeA+mteVUI9P70OG/opH47FeV5AWeaNWWgqJg== +"@unrs/resolver-binding-linux-x64-musl@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.7.0.tgz#a447f7261958f688950be70a26b79a7955fb10d3" + integrity sha512-gHIw42dmnVcw7osjNPRybaXhONhggWkkzqiOZzXco1q3OKkn4KsbDylATeemnq3TP+L1BrzSqzl0H9UTJ6ji+w== + +"@unrs/resolver-binding-wasm32-wasi@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.7.0.tgz#fc9c486ffddef353daef71488f8f77e4de44dd8b" + integrity sha512-yq7POusv63/yTkNTaNsnXU/SAcBzckHyk1oYrDXqjS1m/goaWAaU9J9HrsovgTHkljxTcDd6PMAsJ5WZVBuGEQ== dependencies: - "@napi-rs/wasm-runtime" "^0.2.8" + "@napi-rs/wasm-runtime" "^0.2.9" -"@unrs/resolver-binding-win32-arm64-msvc@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.5.0.tgz#11deb282b8ce73fab26f1d04df0fa4d6363752c2" - integrity sha512-FTZBxLL4SO1mgIM86KykzJmPeTPisBDHQV6xtfDXbTMrentuZ6SdQKJUV5BWaoUK3p8kIULlrCcucqdCnk8Npg== +"@unrs/resolver-binding-win32-arm64-msvc@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.7.0.tgz#c316d889d29293faab926d1260b16a2d4c430ed4" + integrity sha512-/IPZPbdri9jglHonwB3F7EpQZvBK3ObH+g4ma/KDrqTEAECwvgE10Unvo0ox3LQFR/iMMAkVY+sGNMrMiIV/QQ== -"@unrs/resolver-binding-win32-ia32-msvc@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.5.0.tgz#2a5d414912379425bd395ea15901a5dd5febc7c1" - integrity sha512-i5bB7vJ1waUsFciU/FKLd4Zw0VnAkvhiJ4//jYQXyDUuiLKodmtQZVTcOPU7pp97RrNgCFtXfC1gnvj/DHPJTw== +"@unrs/resolver-binding-win32-ia32-msvc@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.7.0.tgz#14c9e08990dd0cf10d4962c40e9b368ea06b9789" + integrity sha512-NGVKbHEdrLuJdpcuGqV5zXO3v8t4CWOs0qeCGjO47RiwwufOi/yYcrtxtCzZAaMPBrffHL7c6tJ1Hxr17cPUGg== -"@unrs/resolver-binding-win32-x64-msvc@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.5.0.tgz#5768c6bba4a27833a48a8a77e50eb01b520d0962" - integrity sha512-wAvXp4k7jhioi4SebXW/yfzzYwsUCr9kIX4gCsUFKpCTUf8Mi7vScJXI3S+kupSUf0LbVHudR8qBbe2wFMSNUw== +"@unrs/resolver-binding-win32-x64-msvc@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.7.0.tgz#c130ae8c0ce56dd1fe952d44fe95a6f9a91cccb6" + integrity sha512-Jf14pKofg58DIwcZv4Wt9AyVVe7bSJP8ODz+EP9nG/rho08FQzan0VOJk1g6/BNE1RkoYd+lRTWK+/BgH12qoQ== "@wry/context@^0.4.0": version "0.4.4" @@ -4605,17 +4610,17 @@ eslint-import-resolver-node@^0.3.9: is-core-module "^2.13.0" resolve "^1.22.4" -eslint-import-resolver-typescript@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.3.2.tgz#1d2371be6d073bade177ee04a4548dbacdc334c0" - integrity sha512-T2LqBXj87ndEC9t1LrDiPkzalSFzD4rrXr6BTzGdgMx1jdQM4T972guQvg7Ih+LNO51GURXI/qMHS5GF3h1ilw== +eslint-import-resolver-typescript@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.3.4.tgz#3d04161698925b5dc9c297966442c2761a319de4" + integrity sha512-buzw5z5VtiQMysYLH9iW9BV04YyZebsw+gPi+c4FCjfS9i6COYOrEWw9t3m3wA9PFBfqcBCqWf32qrXLbwafDw== dependencies: debug "^4.4.0" get-tsconfig "^4.10.0" is-bun-module "^2.0.0" stable-hash "^0.0.5" - tinyglobby "^0.2.12" - unrs-resolver "^1.4.1" + tinyglobby "^0.2.13" + unrs-resolver "^1.6.3" eslint-module-utils@^2.12.0: version "2.12.0" @@ -5048,10 +5053,10 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" -fdir@^6.4.3: - version "6.4.3" - resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.3.tgz#011cdacf837eca9b811c89dbb902df714273db72" - integrity sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw== +fdir@^6.4.4: + version "6.4.4" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.4.tgz#1cfcf86f875a883e19a8fab53622cfe992e8d2f9" + integrity sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg== file-entry-cache@^6.0.1: version "6.0.1" @@ -7701,6 +7706,11 @@ nanoid@^3.3.6: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== +napi-postinstall@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/napi-postinstall/-/napi-postinstall-0.1.6.tgz#7682101f43fc66c233b625ee8ebf07826c6eedde" + integrity sha512-w1bClprmjwpybo+7M1Rd0N4QK5Ein8kH/1CQ0Wv8Q9vrLbDMakxc4rZpv8zYc8RVErUELJlFhM8UzOF3IqlYKw== + natural-compare-lite@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" @@ -9578,12 +9588,12 @@ timers-ext@^0.1.7: es5-ext "^0.10.64" next-tick "^1.1.0" -tinyglobby@^0.2.12: - version "0.2.12" - resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.12.tgz#ac941a42e0c5773bd0b5d08f32de82e74a1a61b5" - integrity sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww== +tinyglobby@^0.2.13: + version "0.2.13" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.13.tgz#a0e46515ce6cbcd65331537e57484af5a7b2ff7e" + integrity sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw== dependencies: - fdir "^6.4.3" + fdir "^6.4.4" picomatch "^4.0.2" title@~3.4.1: @@ -10055,27 +10065,30 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= -unrs-resolver@^1.4.1: - version "1.5.0" - resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.5.0.tgz#d0a608f08321d8e90ba8eb10a3240e7995997275" - integrity sha512-6aia3Oy7SEe0MuUGQm2nsyob0L2+g57w178K5SE/3pvSGAIp28BB2O921fKx424Ahc/gQ6v0DXFbhcpyhGZdOA== +unrs-resolver@^1.6.3: + version "1.7.0" + resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.7.0.tgz#2d1523d0a9c9271d0dc5b400520b776b947893ea" + integrity sha512-b76tVoT9KPniDY1GoYghDUQX20gjzXm/TONfHfgayLaiuo+oGyT9CsQkGCEJs+1/uryVBEOGOt3yYWDXbJhL7g== + dependencies: + napi-postinstall "^0.1.6" optionalDependencies: - "@unrs/resolver-binding-darwin-arm64" "1.5.0" - "@unrs/resolver-binding-darwin-x64" "1.5.0" - "@unrs/resolver-binding-freebsd-x64" "1.5.0" - "@unrs/resolver-binding-linux-arm-gnueabihf" "1.5.0" - "@unrs/resolver-binding-linux-arm-musleabihf" "1.5.0" - "@unrs/resolver-binding-linux-arm64-gnu" "1.5.0" - "@unrs/resolver-binding-linux-arm64-musl" "1.5.0" - "@unrs/resolver-binding-linux-ppc64-gnu" "1.5.0" - "@unrs/resolver-binding-linux-riscv64-gnu" "1.5.0" - "@unrs/resolver-binding-linux-s390x-gnu" "1.5.0" - "@unrs/resolver-binding-linux-x64-gnu" "1.5.0" - "@unrs/resolver-binding-linux-x64-musl" "1.5.0" - "@unrs/resolver-binding-wasm32-wasi" "1.5.0" - "@unrs/resolver-binding-win32-arm64-msvc" "1.5.0" - "@unrs/resolver-binding-win32-ia32-msvc" "1.5.0" - "@unrs/resolver-binding-win32-x64-msvc" "1.5.0" + "@unrs/resolver-binding-darwin-arm64" "1.7.0" + "@unrs/resolver-binding-darwin-x64" "1.7.0" + "@unrs/resolver-binding-freebsd-x64" "1.7.0" + "@unrs/resolver-binding-linux-arm-gnueabihf" "1.7.0" + "@unrs/resolver-binding-linux-arm-musleabihf" "1.7.0" + "@unrs/resolver-binding-linux-arm64-gnu" "1.7.0" + "@unrs/resolver-binding-linux-arm64-musl" "1.7.0" + "@unrs/resolver-binding-linux-ppc64-gnu" "1.7.0" + "@unrs/resolver-binding-linux-riscv64-gnu" "1.7.0" + "@unrs/resolver-binding-linux-riscv64-musl" "1.7.0" + "@unrs/resolver-binding-linux-s390x-gnu" "1.7.0" + "@unrs/resolver-binding-linux-x64-gnu" "1.7.0" + "@unrs/resolver-binding-linux-x64-musl" "1.7.0" + "@unrs/resolver-binding-wasm32-wasi" "1.7.0" + "@unrs/resolver-binding-win32-arm64-msvc" "1.7.0" + "@unrs/resolver-binding-win32-ia32-msvc" "1.7.0" + "@unrs/resolver-binding-win32-x64-msvc" "1.7.0" update-browserslist-db@^1.1.0: version "1.1.0" From 8d8315eb19e613635c23ef30047eda0dee661dea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 26 Apr 2025 11:21:30 +0000 Subject: [PATCH 42/54] build(deps-dev): bump cypress from 14.3.1 to 14.3.2 in the cypress group (#8442) Bumps the cypress group with 1 update: [cypress](https://github.com/cypress-io/cypress). Updates `cypress` from 14.3.1 to 14.3.2 - [Release notes](https://github.com/cypress-io/cypress/releases) - [Changelog](https://github.com/cypress-io/cypress/blob/develop/CHANGELOG.md) - [Commits](https://github.com/cypress-io/cypress/compare/v14.3.1...v14.3.2) --- updated-dependencies: - dependency-name: cypress dependency-version: 14.3.2 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: cypress ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4174bf891..17de00757 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "auto-changelog": "^2.5.0", "bcryptjs": "^3.0.2", "cross-env": "^7.0.3", - "cypress": "^14.3.1", + "cypress": "^14.3.2", "cypress-network-idle": "^1.15.0", "date-fns": "^3.6.0", "dotenv": "^16.5.0", @@ -7762,9 +7762,9 @@ "optional": true }, "node_modules/cypress": { - "version": "14.3.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-14.3.1.tgz", - "integrity": "sha512-/2q06qvHMK3PNiadnRW1Je0lJ43gAFPQJUAK2zIxjr22kugtWxVQznTBLVu1AvRH+RP3oWZhCdWqiEi+0NuqCg==", + "version": "14.3.2", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-14.3.2.tgz", + "integrity": "sha512-n+yGD2ZFFKgy7I3YtVpZ7BcFYrrDMcKj713eOZdtxPttpBjCyw/R8dLlFSsJPouneGN7A/HOSRyPJ5+3/gKDoA==", "dev": true, "hasInstallScript": true, "license": "MIT", diff --git a/package.json b/package.json index e86458b61..c208969f5 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "auto-changelog": "^2.5.0", "bcryptjs": "^3.0.2", "cross-env": "^7.0.3", - "cypress": "^14.3.1", + "cypress": "^14.3.2", "cypress-network-idle": "^1.15.0", "date-fns": "^3.6.0", "dotenv": "^16.5.0", diff --git a/yarn.lock b/yarn.lock index a5a09d086..1e3983be3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4074,10 +4074,10 @@ cypress-network-idle@^1.15.0: resolved "https://registry.yarnpkg.com/cypress-network-idle/-/cypress-network-idle-1.15.0.tgz#e249f08695a46f1ddce18a95d5293937f277cbb3" integrity sha512-8zU16zhc7S3nMl1NTEEcNsZYlJy/ZzP2zPTTrngGxyXH32Ipake/xfHLZsgrzeWCieiS2AVhQsakhWqFzO3hpw== -cypress@^14.3.1: - version "14.3.1" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-14.3.1.tgz#b0570c0e5b198d930a2c0f640d099e777bec2d2f" - integrity sha512-/2q06qvHMK3PNiadnRW1Je0lJ43gAFPQJUAK2zIxjr22kugtWxVQznTBLVu1AvRH+RP3oWZhCdWqiEi+0NuqCg== +cypress@^14.3.2: + version "14.3.2" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-14.3.2.tgz#04a6ea66c1715119ef41dda5851d75801cc1e226" + integrity sha512-n+yGD2ZFFKgy7I3YtVpZ7BcFYrrDMcKj713eOZdtxPttpBjCyw/R8dLlFSsJPouneGN7A/HOSRyPJ5+3/gKDoA== dependencies: "@cypress/request" "^3.0.8" "@cypress/xvfb" "^1.2.4" From 0d8552cb000be76322ba67bd956ba3ff367eaa02 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 26 Apr 2025 17:23:13 +0200 Subject: [PATCH 43/54] build(deps): bump docker/build-push-action from 6.15.0 to 6.16.0 (#8444) Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.15.0 to 6.16.0. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/471d1dc4e07e5cdedd4c2171150001c434f0b7a4...14487ce63c7a62a4a324b0bfb37086795e31c6c1) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-version: 6.16.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker-push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-push.yml b/.github/workflows/docker-push.yml index 406d8304b..cc84c6e3e 100644 --- a/.github/workflows/docker-push.yml +++ b/.github/workflows/docker-push.yml @@ -81,7 +81,7 @@ jobs: type=sha - name: Build and push Docker images id: push - uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 + uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 with: context: ${{ matrix.app.context }} target: ${{ matrix.app.target }} From 7c37a239e26a3bd8c3fc8852124c142abc4e568f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 26 Apr 2025 15:53:08 +0000 Subject: [PATCH 44/54] build(deps-dev): bump @types/node from 22.14.1 to 22.15.2 in /backend (#8446) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.14.1 to 22.15.2. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 22.15.2 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- backend/package.json | 2 +- backend/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/package.json b/backend/package.json index 39a0016bc..b04ab206f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -104,7 +104,7 @@ "@eslint-community/eslint-plugin-eslint-comments": "^4.5.0", "@faker-js/faker": "9.7.0", "@types/jest": "^29.5.14", - "@types/node": "^22.14.1", + "@types/node": "^22.15.2", "@types/uuid": "~9.0.1", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", diff --git a/backend/yarn.lock b/backend/yarn.lock index f37877286..811fbbe32 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -2079,10 +2079,10 @@ "@types/node" "*" form-data "^3.0.0" -"@types/node@*", "@types/node@>=6", "@types/node@^22.14.1": - version "22.14.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.14.1.tgz#53b54585cec81c21eee3697521e31312d6ca1e6f" - integrity sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw== +"@types/node@*", "@types/node@>=6", "@types/node@^22.15.2": + version "22.15.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.2.tgz#1db55aa64618ee93a58c8912f74beefe44aca905" + integrity sha512-uKXqKN9beGoMdBfcaTY1ecwz6ctxuJAcUlwE55938g0ZJ8lRxwAZqRz2AJ4pzpt5dHdTPMB863UZ0ESiFUcP7A== dependencies: undici-types "~6.21.0" From ce19450b0d804ffc15173d3e527618d46927a888 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 26 Apr 2025 16:23:22 +0000 Subject: [PATCH 45/54] build(deps-dev): bump nodemon from 3.1.9 to 3.1.10 in /backend (#8447) Bumps [nodemon](https://github.com/remy/nodemon) from 3.1.9 to 3.1.10. - [Release notes](https://github.com/remy/nodemon/releases) - [Commits](https://github.com/remy/nodemon/compare/v3.1.9...v3.1.10) --- updated-dependencies: - dependency-name: nodemon dependency-version: 3.1.10 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- backend/package.json | 2 +- backend/yarn.lock | 17 +++++------------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/backend/package.json b/backend/package.json index b04ab206f..1c0e5f6ad 100644 --- a/backend/package.json +++ b/backend/package.json @@ -121,7 +121,7 @@ "eslint-plugin-promise": "^7.2.1", "eslint-plugin-security": "^3.0.1", "jest": "^29.7.0", - "nodemon": "~3.1.9", + "nodemon": "~3.1.10", "prettier": "^3.5.3", "require-json5": "^1.3.0", "rosie": "^2.1.1", diff --git a/backend/yarn.lock b/backend/yarn.lock index 811fbbe32..aefdcf732 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -7566,14 +7566,7 @@ minimatch@^5.0.1: dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.1, minimatch@^9.0.4: - version "9.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" - integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^9.0.5: +minimatch@^9.0.1, minimatch@^9.0.4, minimatch@^9.0.5: version "9.0.5" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== @@ -7873,10 +7866,10 @@ nodemailer@^6.10.1: resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.10.1.tgz#cbc434c54238f83a51c07eabd04e2b3e832da623" integrity sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA== -nodemon@~3.1.9: - version "3.1.9" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.1.9.tgz#df502cdc3b120e1c3c0c6e4152349019efa7387b" - integrity sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg== +nodemon@~3.1.10: + version "3.1.10" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.1.10.tgz#5015c5eb4fffcb24d98cf9454df14f4fecec9bc1" + integrity sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw== dependencies: chokidar "^3.5.2" debug "^4" From 3c853d57379d5f4df13e7c6603ada0f949f27ee7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 26 Apr 2025 16:57:17 +0000 Subject: [PATCH 46/54] build(deps): bump peter-evans/repository-dispatch (#8443) Bumps [peter-evans/repository-dispatch](https://github.com/peter-evans/repository-dispatch) from 7d980a9b9f8ecf8955ea90507b3ed89122f53215 to 44966f0098fd4ab26380bb099e1edf6d57eb2c90. - [Release notes](https://github.com/peter-evans/repository-dispatch/releases) - [Commits](https://github.com/peter-evans/repository-dispatch/compare/7d980a9b9f8ecf8955ea90507b3ed89122f53215...44966f0098fd4ab26380bb099e1edf6d57eb2c90) --- updated-dependencies: - dependency-name: peter-evans/repository-dispatch dependency-version: 44966f0098fd4ab26380bb099e1edf6d57eb2c90 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b66413f22..425da269b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -64,7 +64,7 @@ jobs: echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV - run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV #- name: Repository Dispatch - # uses: peter-evans/repository-dispatch@7d980a9b9f8ecf8955ea90507b3ed89122f53215 # v3.0.0 + # uses: peter-evans/repository-dispatch@44966f0098fd4ab26380bb099e1edf6d57eb2c90 # v3.0.0 # with: # token: ${{ github.token }} # event-type: trigger-ocelot-build-success @@ -72,7 +72,7 @@ jobs: # client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "VERSION": "${VERSION}", "BUILD_DATE": "${BUILD_DATE}", "BUILD_COMMIT": "${BUILD_COMMIT}", "BUILD_VERSION": "${BUILD_VERSION}"}' - name: Repository Dispatch stage.ocelot.social - uses: peter-evans/repository-dispatch@7d980a9b9f8ecf8955ea90507b3ed89122f53215 # v3.0.0 + uses: peter-evans/repository-dispatch@44966f0098fd4ab26380bb099e1edf6d57eb2c90 # v3.0.0 with: token: ${{ secrets.OCELOT_PUBLISH_EVENT_PAT }} # this token is required to access the other repository event-type: trigger-ocelot-build-success @@ -80,7 +80,7 @@ jobs: client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "GITHUB_RUN_NUMBER": "${{ env.GITHUB_RUN_NUMBER }}", "VERSION": "${VERSION}", "BUILD_DATE": "${BUILD_DATE}", "BUILD_COMMIT": "${BUILD_COMMIT}", "BUILD_VERSION": "${BUILD_VERSION}"}' - name: Repository Dispatch stage.yunite.me - uses: peter-evans/repository-dispatch@7d980a9b9f8ecf8955ea90507b3ed89122f53215 # v3.0.0 + uses: peter-evans/repository-dispatch@44966f0098fd4ab26380bb099e1edf6d57eb2c90 # v3.0.0 with: token: ${{ secrets.OCELOT_PUBLISH_EVENT_PAT }} # this token is required to access the other repository event-type: trigger-ocelot-build-success From ba0cc147e79d2618161a3293f926a82c7256cc3d Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 26 Apr 2025 19:25:27 +0200 Subject: [PATCH 47/54] fix(webapp): fix admin badges settings (#8438) * Remove proxy from nuxt.config, instead add proxy filter * Show message when there are no badges --- .../Admin/Badges/BadgesSection.spec.js | 37 +++++++++++++------ .../features/Admin/Badges/BadgesSection.vue | 7 +++- .../__snapshots__/BadgesSection.spec.js.snap | 22 ++++++++++- webapp/locales/de.json | 1 + webapp/locales/en.json | 1 + webapp/locales/es.json | 1 + webapp/locales/fr.json | 1 + webapp/locales/it.json | 1 + webapp/locales/nl.json | 1 + webapp/locales/pl.json | 1 + webapp/locales/pt.json | 1 + webapp/locales/ru.json | 1 + webapp/nuxt.config.js | 9 ----- webapp/pages/admin/users/_id.spec.js | 5 +++ webapp/pages/admin/users/_id.vue | 5 ++- 15 files changed, 70 insertions(+), 24 deletions(-) diff --git a/webapp/components/_new/features/Admin/Badges/BadgesSection.spec.js b/webapp/components/_new/features/Admin/Badges/BadgesSection.spec.js index 8baddc692..8abf8d679 100644 --- a/webapp/components/_new/features/Admin/Badges/BadgesSection.spec.js +++ b/webapp/components/_new/features/Admin/Badges/BadgesSection.spec.js @@ -21,26 +21,41 @@ const badge2 = { describe('Admin/BadgesSection', () => { let wrapper - const Wrapper = () => { + const Wrapper = (withBadges = true) => { return render(BadgesSection, { localVue, propsData: { - badges: [badge1, badge2], + badges: withBadges ? [badge1, badge2] : [], + }, + mocks: { + $t: jest.fn((t) => t), }, }) } - beforeEach(() => { - wrapper = Wrapper() + describe('without badges', () => { + beforeEach(() => { + wrapper = Wrapper(false) + }) + + it('renders', () => { + expect(wrapper.baseElement).toMatchSnapshot() + }) }) - it('renders', () => { - expect(wrapper.baseElement).toMatchSnapshot() - }) + describe('with badges', () => { + beforeEach(() => { + wrapper = Wrapper(true) + }) - it('emits toggleButton', async () => { - const button = screen.getByAltText(badge1.description) - await fireEvent.click(button) - expect(wrapper.emitted().toggleBadge[0][0]).toEqual(badge1) + it('renders', () => { + expect(wrapper.baseElement).toMatchSnapshot() + }) + + it('emits toggleButton', async () => { + const button = screen.getByAltText(badge1.description) + await fireEvent.click(button) + expect(wrapper.emitted().toggleBadge[0][0]).toEqual(badge1) + }) }) }) diff --git a/webapp/components/_new/features/Admin/Badges/BadgesSection.vue b/webapp/components/_new/features/Admin/Badges/BadgesSection.vue index 8ff9da7ed..fc89d2a50 100644 --- a/webapp/components/_new/features/Admin/Badges/BadgesSection.vue +++ b/webapp/components/_new/features/Admin/Badges/BadgesSection.vue @@ -1,16 +1,19 @@ diff --git a/webapp/components/_new/features/Admin/Badges/__snapshots__/BadgesSection.spec.js.snap b/webapp/components/_new/features/Admin/Badges/__snapshots__/BadgesSection.spec.js.snap index c09a50725..a78f44edc 100644 --- a/webapp/components/_new/features/Admin/Badges/__snapshots__/BadgesSection.spec.js.snap +++ b/webapp/components/_new/features/Admin/Badges/__snapshots__/BadgesSection.spec.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Admin/BadgesSection renders 1`] = ` +exports[`Admin/BadgesSection with badges renders 1`] = `
`; + +exports[`Admin/BadgesSection without badges renders 1`] = ` + +
+
+

+ +

+ +
+ + admin.badges.noBadges + +
+
+
+ +`; diff --git a/webapp/locales/de.json b/webapp/locales/de.json index 4fe39ce24..6b588f746 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -12,6 +12,7 @@ "admin": { "badges": { "description": "Stelle die verfügbaren Auszeichnungen für diesen Nutzer ein.", + "noBadges": "Keine Auszeichnungen vorhanden.", "revokeTrophy": { "error": "Trophäe konnte nicht widerrufen werden!", "success": "Trophäe erfolgreich widerrufen" diff --git a/webapp/locales/en.json b/webapp/locales/en.json index bdd9cdefb..24fd16440 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -12,6 +12,7 @@ "admin": { "badges": { "description": "Configure the available badges for this user", + "noBadges": "There are no badges available", "revokeTrophy": { "error": "Trophy could not be revoked!", "success": "Trophy successfully revoked!" diff --git a/webapp/locales/es.json b/webapp/locales/es.json index 60e65ca20..5bc6b1b6d 100644 --- a/webapp/locales/es.json +++ b/webapp/locales/es.json @@ -12,6 +12,7 @@ "admin": { "badges": { "description": null, + "noBadges": null, "revokeTrophy": { "error": null, "success": null diff --git a/webapp/locales/fr.json b/webapp/locales/fr.json index e7b4fcd4a..90164c47a 100644 --- a/webapp/locales/fr.json +++ b/webapp/locales/fr.json @@ -12,6 +12,7 @@ "admin": { "badges": { "description": null, + "noBadges": null, "revokeTrophy": { "error": null, "success": null diff --git a/webapp/locales/it.json b/webapp/locales/it.json index 703b27fff..1674a9cb3 100644 --- a/webapp/locales/it.json +++ b/webapp/locales/it.json @@ -12,6 +12,7 @@ "admin": { "badges": { "description": null, + "noBadges": null, "revokeTrophy": { "error": null, "success": null diff --git a/webapp/locales/nl.json b/webapp/locales/nl.json index f6a53d0c8..0944dc472 100644 --- a/webapp/locales/nl.json +++ b/webapp/locales/nl.json @@ -12,6 +12,7 @@ "admin": { "badges": { "description": null, + "noBadges": null, "revokeTrophy": { "error": null, "success": null diff --git a/webapp/locales/pl.json b/webapp/locales/pl.json index 4e928f417..ba5a61bca 100644 --- a/webapp/locales/pl.json +++ b/webapp/locales/pl.json @@ -12,6 +12,7 @@ "admin": { "badges": { "description": null, + "noBadges": null, "revokeTrophy": { "error": null, "success": null diff --git a/webapp/locales/pt.json b/webapp/locales/pt.json index 005eb77f4..6b6f07132 100644 --- a/webapp/locales/pt.json +++ b/webapp/locales/pt.json @@ -12,6 +12,7 @@ "admin": { "badges": { "description": null, + "noBadges": null, "revokeTrophy": { "error": null, "success": null diff --git a/webapp/locales/ru.json b/webapp/locales/ru.json index bc862bcc2..7bc33e628 100644 --- a/webapp/locales/ru.json +++ b/webapp/locales/ru.json @@ -12,6 +12,7 @@ "admin": { "badges": { "description": null, + "noBadges": null, "revokeTrophy": { "error": null, "success": null diff --git a/webapp/nuxt.config.js b/webapp/nuxt.config.js index 07cfa6bc4..263c3f149 100644 --- a/webapp/nuxt.config.js +++ b/webapp/nuxt.config.js @@ -207,15 +207,6 @@ export default { 'X-API-TOKEN': CONFIG.BACKEND_TOKEN, }, }, - '/img': { - // make this configurable (nuxt-dotenv) - target: CONFIG.GRAPHQL_URI, - toProxy: true, // cloudflare needs that - headers: { - 'X-UI-Request': true, - 'X-API-TOKEN': CONFIG.BACKEND_TOKEN, - }, - }, }, // Give apollo module options diff --git a/webapp/pages/admin/users/_id.spec.js b/webapp/pages/admin/users/_id.spec.js index 933de58de..d38b13022 100644 --- a/webapp/pages/admin/users/_id.spec.js +++ b/webapp/pages/admin/users/_id.spec.js @@ -58,6 +58,11 @@ describe('.vue', () => { query: jest.fn(), }, mutate: jest.fn(), + queries: { + Badge: { + loading: false, + }, + }, }, $toast: { success: jest.fn(), diff --git a/webapp/pages/admin/users/_id.vue b/webapp/pages/admin/users/_id.vue index 808e1653a..a6c4dafaa 100644 --- a/webapp/pages/admin/users/_id.vue +++ b/webapp/pages/admin/users/_id.vue @@ -8,7 +8,7 @@ {{ $t('admin.badges.description') }} - + userBadge.id === badge.id), })) }, + isLoadingBadges() { + return this.$apollo.queries.Badge.loading + }, }, methods: { toggleBadge(badge) { From a5ee90a95d0e7b8562a2dac8d8b0a8455fd00356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Mon, 28 Apr 2025 15:32:23 +0200 Subject: [PATCH 48/54] fix(webapp): refine little things (#8382) * Refine locals of some internal pages headlines * Fix tool tip text * Fix 'email' -> 'e-mail' * Syncronize 'metadata.ts' with webapp * Refine e-mail notifications * Adjust notification settings buttons * Refine third party setting * Fix post teaser counter icon tooltips translations * Refine e-mail notifications * Refine third party setting * notification spec snapshot --------- Co-authored-by: Ulf Gebhardt --- backend/src/config/metadata.ts | 3 +- webapp/components/PostTeaser/PostTeaser.vue | 20 ++++- webapp/locales/de.json | 32 ++++---- webapp/locales/en.json | 34 ++++---- .../__snapshots__/notifications.spec.js.snap | 79 ++++++++++--------- webapp/pages/settings/embeds.vue | 47 +++++++---- webapp/pages/settings/notifications.vue | 32 +++++--- 7 files changed, 147 insertions(+), 100 deletions(-) diff --git a/backend/src/config/metadata.ts b/backend/src/config/metadata.ts index 282fcb655..9c87818ae 100644 --- a/backend/src/config/metadata.ts +++ b/backend/src/config/metadata.ts @@ -1,9 +1,10 @@ // this file is duplicated in `backend/src/config/metadata` and `webapp/constants/metadata.js` and replaced on rebranding export default { APPLICATION_NAME: 'ocelot.social', - APPLICATION_SHORT_NAME: 'ocelot', + APPLICATION_SHORT_NAME: 'ocelot.social', APPLICATION_DESCRIPTION: 'ocelot.social Community Network', COOKIE_NAME: 'ocelot-social-token', ORGANIZATION_NAME: 'ocelot.social Community', ORGANIZATION_JURISDICTION: 'City of Angels', + THEME_COLOR: 'rgb(23, 181, 63)', // $color-primary – as the main color in general. e.g. the color in the background of the app that is visible behind the transparent iPhone status bar to name one use case, or the current color of SVGs to name another use case } diff --git a/webapp/components/PostTeaser/PostTeaser.vue b/webapp/components/PostTeaser/PostTeaser.vue index ad43a9d31..32a07d5a3 100644 --- a/webapp/components/PostTeaser/PostTeaser.vue +++ b/webapp/components/PostTeaser/PostTeaser.vue @@ -72,22 +72,34 @@

settings.notifications.post @@ -66,11 +66,11 @@ exports[`notifications.vue mount renders 1`] = `

settings.notifications.group @@ -155,42 +155,47 @@ exports[`notifications.vue mount renders 1`] = `

- - + - settings.notifications.checkAll - - - - - - + +
diff --git a/webapp/pages/settings/embeds.vue b/webapp/pages/settings/embeds.vue index 53db65e5e..aee47dfe7 100644 --- a/webapp/pages/settings/embeds.vue +++ b/webapp/pages/settings/embeds.vue @@ -17,20 +17,29 @@ {{ $t('settings.embeds.status.change.question') }} - - {{ $t('settings.embeds.status.change.deny') }} - - - {{ $t('settings.embeds.status.change.allow') }} - - -

{{ $t('settings.embeds.info-description') }}

-
    -
  • - {{ provider.provider_name }}, - {{ provider.provider_url }} -
  • -
+ + + {{ $t('settings.embeds.status.change.deny') }} + + + {{ $t('settings.embeds.status.change.allow') }} + + +

{{ $t('settings.embeds.info-description') }}

+ +
    +
  • + + {{ provider.provider_name }}, + {{ provider.provider_url }} + +
  • +
+
@@ -93,3 +102,13 @@ export default { }, } + + diff --git a/webapp/pages/settings/notifications.vue b/webapp/pages/settings/notifications.vue index 36e0d9081..8a383dc4f 100644 --- a/webapp/pages/settings/notifications.vue +++ b/webapp/pages/settings/notifications.vue @@ -1,8 +1,13 @@ @@ -138,4 +145,7 @@ export default { .label { margin-left: $space-xx-small; } +button + button { + margin-left: $space-x-small; +} From fffaebcbca91410c7e77ea3df08b61453004cfe5 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 28 Apr 2025 16:39:54 +0200 Subject: [PATCH 49/54] Replace edit link by pencil button (#8453) We show now a round button with pencil icon to edit the badges. --- webapp/locales/de.json | 3 +- webapp/locales/en.json | 3 +- webapp/locales/es.json | 3 +- webapp/locales/fr.json | 3 +- webapp/locales/it.json | 3 +- webapp/locales/nl.json | 3 +- webapp/locales/pl.json | 3 +- webapp/locales/pt.json | 3 +- webapp/locales/ru.json | 3 +- .../users/__snapshots__/index.spec.js.snap | 32 +++++++++++++++---- webapp/pages/admin/users/index.vue | 6 +++- 11 files changed, 40 insertions(+), 25 deletions(-) diff --git a/webapp/locales/de.json b/webapp/locales/de.json index 96a2b323a..df050b191 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -98,8 +98,7 @@ "number": "Nr.", "role": "Rolle", "slug": "Alias" - }, - "edit": "Bearbeiten" + } } } }, diff --git a/webapp/locales/en.json b/webapp/locales/en.json index 760937b06..ecd0ec18d 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -98,8 +98,7 @@ "number": "No.", "role": "Role", "slug": "Slug" - }, - "edit": "Edit" + } } } }, diff --git a/webapp/locales/es.json b/webapp/locales/es.json index 5bc6b1b6d..15096b9d8 100644 --- a/webapp/locales/es.json +++ b/webapp/locales/es.json @@ -98,8 +98,7 @@ "number": "No.", "role": "Rol", "slug": "Alias" - }, - "edit": null + } } } }, diff --git a/webapp/locales/fr.json b/webapp/locales/fr.json index 90164c47a..2da2a9801 100644 --- a/webapp/locales/fr.json +++ b/webapp/locales/fr.json @@ -98,8 +98,7 @@ "number": "Num.", "role": "Rôle", "slug": "Slug" - }, - "edit": null + } } } }, diff --git a/webapp/locales/it.json b/webapp/locales/it.json index 1674a9cb3..485abff3a 100644 --- a/webapp/locales/it.json +++ b/webapp/locales/it.json @@ -98,8 +98,7 @@ "number": null, "role": null, "slug": null - }, - "edit": null + } } } }, diff --git a/webapp/locales/nl.json b/webapp/locales/nl.json index 0944dc472..40f9aca2e 100644 --- a/webapp/locales/nl.json +++ b/webapp/locales/nl.json @@ -98,8 +98,7 @@ "number": null, "role": null, "slug": null - }, - "edit": null + } } } }, diff --git a/webapp/locales/pl.json b/webapp/locales/pl.json index ba5a61bca..ee332b84b 100644 --- a/webapp/locales/pl.json +++ b/webapp/locales/pl.json @@ -98,8 +98,7 @@ "number": null, "role": null, "slug": null - }, - "edit": null + } } } }, diff --git a/webapp/locales/pt.json b/webapp/locales/pt.json index 6b6f07132..54f9b5d99 100644 --- a/webapp/locales/pt.json +++ b/webapp/locales/pt.json @@ -98,8 +98,7 @@ "number": "N.º", "role": "Função", "slug": "Slug" - }, - "edit": null + } } } }, diff --git a/webapp/locales/ru.json b/webapp/locales/ru.json index 7bc33e628..4d2e2a357 100644 --- a/webapp/locales/ru.json +++ b/webapp/locales/ru.json @@ -98,8 +98,7 @@ "number": "№", "role": "Роль", "slug": "Алиас" - }, - "edit": null + } } } }, diff --git a/webapp/pages/admin/users/__snapshots__/index.spec.js.snap b/webapp/pages/admin/users/__snapshots__/index.spec.js.snap index b72a2617f..0fff4016b 100644 --- a/webapp/pages/admin/users/__snapshots__/index.spec.js.snap +++ b/webapp/pages/admin/users/__snapshots__/index.spec.js.snap @@ -709,9 +709,19 @@ exports[`Users given badges are enabled renders 1`] = ` - - admin.users.table.edit - + @@ -793,9 +803,19 @@ exports[`Users given badges are enabled renders 1`] = ` - - admin.users.table.edit - + diff --git a/webapp/pages/admin/users/index.vue b/webapp/pages/admin/users/index.vue index fd08f1e0c..0bd592bad 100644 --- a/webapp/pages/admin/users/index.vue +++ b/webapp/pages/admin/users/index.vue @@ -70,7 +70,7 @@ params: { id: scope.row.id }, }" > - {{ $t('admin.users.table.edit') }} + @@ -224,4 +224,8 @@ export default { .admin-users > .base-card:first-child { margin-bottom: $space-small; } + +.ds-table-col { + vertical-align: middle; +} From d7d8a242cdb51cdbf83e766daf322bf2e2c36d1a Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Mon, 28 Apr 2025 18:17:18 +0200 Subject: [PATCH 50/54] fix(backend): fixes for branding (#8449) * copy from branding folder to backend public folder provide default branding/public folder * copy public folder correctly * copy files again for providers.json * copy more public folders * more copy * revert change * fix naming of called script when using db:data:branding * prod command for branding data * close database connection * lint fixes * increase test timeout again --- backend/Dockerfile | 3 ++- backend/branding/public/.gitkeep | 0 backend/package.json | 5 +++-- backend/src/db/data-branding.ts | 11 ++++++++--- backend/test/setup.ts | 2 +- 5 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 backend/branding/public/.gitkeep diff --git a/backend/Dockerfile b/backend/Dockerfile index e1c244069..1e76cf841 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -24,11 +24,12 @@ ONBUILD COPY ./branding/constants/ src/config/tmp ONBUILD RUN tools/replace-constants.sh ONBUILD COPY ./branding/email/ src/middleware/helpers/email/ ONBUILD COPY ./branding/data/ src/db/data +ONBUILD COPY ./branding/public/ public/ ONBUILD RUN yarn install --production=false --frozen-lockfile --non-interactive ONBUILD RUN yarn run build ONBUILD RUN mkdir /build ONBUILD RUN cp -r ./build /build -ONBUILD RUN cp -r ./public /build/build +ONBUILD RUN cp -r ./public /build ONBUILD RUN cp -r ./package.json yarn.lock /build ONBUILD RUN cd /build && yarn install --production=true --frozen-lockfile --non-interactive diff --git a/backend/branding/public/.gitkeep b/backend/branding/public/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/backend/package.json b/backend/package.json index 1c0e5f6ad..7d884317e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -19,11 +19,12 @@ "db:seed": "ts-node --require tsconfig-paths/register src/db/seed.ts", "db:data:admin": "ts-node --require tsconfig-paths/register src/db/admin.ts", "db:data:badges": "ts-node --require tsconfig-paths/register src/db/badges.ts", - "db:data:branding": "ts-node --require tsconfig-paths/register src/db/data-production.ts", + "db:data:branding": "ts-node --require tsconfig-paths/register src/db/data-branding.ts", "db:data:categories": "ts-node --require tsconfig-paths/register src/db/categories.ts", "db:migrate": "migrate --compiler 'ts:./src/db/compiler.ts' --migrations-dir ./src/db/migrations --store ./src/db/migrate/store.ts", "db:migrate:create": "migrate --compiler 'ts:./src/db/compiler.ts' --migrations-dir ./src/db/migrations --template-file ./src/db/migrate/template.ts --date-format 'yyyymmddHHmmss' create", - "prod:migrate": "migrate --migrations-dir ./build/src/db/migrations --store ./build/src/db/migrate/store.js" + "prod:migrate": "migrate --migrations-dir ./build/src/db/migrations --store ./build/src/db/migrate/store.js", + "prod:db:data:branding": "node build/src/db/data-branding.js" }, "dependencies": { "@babel/cli": "~7.27.0", diff --git a/backend/src/db/data-branding.ts b/backend/src/db/data-branding.ts index e9af41840..eceaf391b 100644 --- a/backend/src/db/data-branding.ts +++ b/backend/src/db/data-branding.ts @@ -1,16 +1,18 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-misused-promises */ /* eslint-disable @typescript-eslint/no-floating-promises */ import { readdir } from 'node:fs/promises' import path from 'node:path' +import { getNeode } from './neo4j' + const dataFolder = path.join(__dirname, 'data/') +const neode = getNeode() ;(async function () { const files = await readdir(dataFolder) - files.forEach(async (file) => { + for await (const file of files) { if (file.slice(0, -3).endsWith('-branding')) { const importedModule = await import(path.join(dataFolder, file)) if (!importedModule.default) { @@ -18,5 +20,8 @@ const dataFolder = path.join(__dirname, 'data/') } await importedModule.default() } - }) + } + + // close database connection + neode.close() })() diff --git a/backend/test/setup.ts b/backend/test/setup.ts index 128830f13..d1d32be5b 100644 --- a/backend/test/setup.ts +++ b/backend/test/setup.ts @@ -1,2 +1,2 @@ // Metascraper takes longer nowadays, double time -// jest.setTimeout(10000) +jest.setTimeout(10000) From 48c7bd0033ecfe1db3effa6982dd6e037596a28e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Mon, 28 Apr 2025 18:58:35 +0200 Subject: [PATCH 51/54] refactor(webapp): make login, registration, password-reset layout brandable (#8440) * Make login, registration, password-reset layout brandable - Rename some variables related to this * Remove experimental code * add lodash types * fix build fix type --------- Co-authored-by: Ulf Gebhardt --- backend/package.json | 1 + backend/src/constants/registration.ts | 7 ++----- backend/src/constants/registrationBranded.ts | 12 ++++++++++++ backend/src/schema/resolvers/embeds/scraper.ts | 7 ++++--- .../resolvers/helpers/generateInviteCode.ts | 4 ++-- .../src/schema/resolvers/helpers/generateNonce.ts | 4 ++-- backend/src/schema/resolvers/inviteCodes.spec.ts | 6 +++--- .../src/schema/resolvers/passwordReset.spec.ts | 4 ++-- backend/src/schema/resolvers/passwordReset.ts | 4 ++-- backend/yarn.lock | 5 +++++ .../Registration/RegistrationSlideInvite.vue | 8 ++++---- .../Registration/RegistrationSlideNonce.vue | 8 ++++---- webapp/constants/login.js | 1 + webapp/constants/loginBranded.js | 8 ++++++++ webapp/constants/registration.js | 7 ++----- webapp/constants/registrationBranded.js | 12 ++++++++++++ webapp/pages/login.vue | 11 +++++++---- webapp/pages/password-reset.vue | 3 ++- webapp/pages/registration.spec.js | 2 +- webapp/pages/registration.vue | 15 +++++++++------ 20 files changed, 85 insertions(+), 44 deletions(-) create mode 100644 backend/src/constants/registrationBranded.ts create mode 100644 webapp/constants/login.js create mode 100644 webapp/constants/loginBranded.js create mode 100644 webapp/constants/registrationBranded.js diff --git a/backend/package.json b/backend/package.json index 7d884317e..2c451ec57 100644 --- a/backend/package.json +++ b/backend/package.json @@ -105,6 +105,7 @@ "@eslint-community/eslint-plugin-eslint-comments": "^4.5.0", "@faker-js/faker": "9.7.0", "@types/jest": "^29.5.14", + "@types/lodash": "^4.17.16", "@types/node": "^22.15.2", "@types/uuid": "~9.0.1", "@typescript-eslint/eslint-plugin": "^5.62.0", diff --git a/backend/src/constants/registration.ts b/backend/src/constants/registration.ts index a08be3521..8ebb40573 100644 --- a/backend/src/constants/registration.ts +++ b/backend/src/constants/registration.ts @@ -1,5 +1,2 @@ -// this file is duplicated in `backend/src/config/metadata` and `webapp/constants/metadata.js` -export default { - NONCE_LENGTH: 5, - INVITE_CODE_LENGTH: 6, -} +// this file is duplicated in `backend/src/config/registration.ts` and `webapp/constants/registration.js` +export default {} diff --git a/backend/src/constants/registrationBranded.ts b/backend/src/constants/registrationBranded.ts new file mode 100644 index 000000000..2ce1d6965 --- /dev/null +++ b/backend/src/constants/registrationBranded.ts @@ -0,0 +1,12 @@ +// this file is duplicated in `backend/src/config/registrationBranded.ts` and `webapp/constants/registrationBranded.js` +import { merge } from 'lodash' + +import registration from '@constants/registration' + +const defaultRegistration = { + NONCE_LENGTH: 5, + INVITE_CODE_LENGTH: 6, + LAYOUT: 'no-header', +} + +export default merge(defaultRegistration, registration) diff --git a/backend/src/schema/resolvers/embeds/scraper.ts b/backend/src/schema/resolvers/embeds/scraper.ts index bcd25046b..a8cd07a76 100644 --- a/backend/src/schema/resolvers/embeds/scraper.ts +++ b/backend/src/schema/resolvers/embeds/scraper.ts @@ -87,8 +87,9 @@ export default async function scrape(url) { throw new ApolloError('Not found', 'NOT_FOUND') } - return { - type: 'link', - ...output, + if (!output.type) { + output.type = 'link' } + + return output } diff --git a/backend/src/schema/resolvers/helpers/generateInviteCode.ts b/backend/src/schema/resolvers/helpers/generateInviteCode.ts index 6e580fab9..980af4593 100644 --- a/backend/src/schema/resolvers/helpers/generateInviteCode.ts +++ b/backend/src/schema/resolvers/helpers/generateInviteCode.ts @@ -1,9 +1,9 @@ -import CONSTANTS_REGISTRATION from '@constants/registration' +import registrationConstants from '@constants/registrationBranded' export default function generateInviteCode() { // 6 random numbers in [ 0, 35 ] are 36 possible numbers (10 [0-9] + 26 [A-Z]) return Array.from( - { length: CONSTANTS_REGISTRATION.INVITE_CODE_LENGTH }, + { length: registrationConstants.INVITE_CODE_LENGTH }, (n: number = Math.floor(Math.random() * 36)) => { // n > 9: it is a letter (ASCII 65 is A) -> 10 + 55 = 65 // else: it is a number (ASCII 48 is 0) -> 0 + 48 = 48 diff --git a/backend/src/schema/resolvers/helpers/generateNonce.ts b/backend/src/schema/resolvers/helpers/generateNonce.ts index 7e0f7542c..b7585b24f 100644 --- a/backend/src/schema/resolvers/helpers/generateNonce.ts +++ b/backend/src/schema/resolvers/helpers/generateNonce.ts @@ -1,9 +1,9 @@ -import CONSTANTS_REGISTRATION from '@constants/registration' +import registrationConstants from '@constants/registrationBranded' // TODO: why this is not used in resolver 'requestPasswordReset'? export default function generateNonce() { return Array.from( - { length: CONSTANTS_REGISTRATION.NONCE_LENGTH }, + { length: registrationConstants.NONCE_LENGTH }, (n: number = Math.floor(Math.random() * 10)) => { return String.fromCharCode(n + 48) }, diff --git a/backend/src/schema/resolvers/inviteCodes.spec.ts b/backend/src/schema/resolvers/inviteCodes.spec.ts index 7d335077a..f44721cc9 100644 --- a/backend/src/schema/resolvers/inviteCodes.spec.ts +++ b/backend/src/schema/resolvers/inviteCodes.spec.ts @@ -5,7 +5,7 @@ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' -import CONSTANTS_REGISTRATION from '@constants/registration' +import registrationConstants from '@constants/registrationBranded' import Factory, { cleanDatabase } from '@db/factories' import { getDriver } from '@db/neo4j' import createServer from '@src/server' @@ -116,7 +116,7 @@ describe('inviteCodes', () => { GenerateInviteCode: { code: expect.stringMatching( new RegExp( - `^[0-9A-Z]{${CONSTANTS_REGISTRATION.INVITE_CODE_LENGTH},${CONSTANTS_REGISTRATION.INVITE_CODE_LENGTH}}$`, + `^[0-9A-Z]{${registrationConstants.INVITE_CODE_LENGTH},${registrationConstants.INVITE_CODE_LENGTH}}$`, ), ), expiresAt: null, @@ -142,7 +142,7 @@ describe('inviteCodes', () => { GenerateInviteCode: { code: expect.stringMatching( new RegExp( - `^[0-9A-Z]{${CONSTANTS_REGISTRATION.INVITE_CODE_LENGTH},${CONSTANTS_REGISTRATION.INVITE_CODE_LENGTH}}$`, + `^[0-9A-Z]{${registrationConstants.INVITE_CODE_LENGTH},${registrationConstants.INVITE_CODE_LENGTH}}$`, ), ), expiresAt: nextWeek.toISOString(), diff --git a/backend/src/schema/resolvers/passwordReset.spec.ts b/backend/src/schema/resolvers/passwordReset.spec.ts index 66823cc5d..d5d08265c 100644 --- a/backend/src/schema/resolvers/passwordReset.spec.ts +++ b/backend/src/schema/resolvers/passwordReset.spec.ts @@ -5,7 +5,7 @@ import { createTestClient } from 'apollo-server-testing' import gql from 'graphql-tag' -import CONSTANTS_REGISTRATION from '@constants/registration' +import registrationConstants from '@constants/registrationBranded' import Factory, { cleanDatabase } from '@db/factories' import { getNeode, getDriver } from '@db/neo4j' import createServer from '@src/server' @@ -118,7 +118,7 @@ describe('passwordReset', () => { const resets = await getAllPasswordResets() const [reset] = resets const { nonce } = reset.properties - expect(nonce).toHaveLength(CONSTANTS_REGISTRATION.NONCE_LENGTH) + expect(nonce).toHaveLength(registrationConstants.NONCE_LENGTH) }) }) }) diff --git a/backend/src/schema/resolvers/passwordReset.ts b/backend/src/schema/resolvers/passwordReset.ts index 3159d7006..f806f7249 100644 --- a/backend/src/schema/resolvers/passwordReset.ts +++ b/backend/src/schema/resolvers/passwordReset.ts @@ -7,7 +7,7 @@ import bcrypt from 'bcryptjs' import { v4 as uuid } from 'uuid' -import CONSTANTS_REGISTRATION from '@constants/registration' +import registrationConstants from '@constants/registrationBranded' import createPasswordReset from './helpers/createPasswordReset' @@ -15,7 +15,7 @@ export default { Mutation: { requestPasswordReset: async (_parent, { email }, { driver }) => { // TODO: why this is generated differntly from 'backend/src/schema/resolvers/helpers/generateNonce.js'? - const nonce = uuid().substring(0, CONSTANTS_REGISTRATION.NONCE_LENGTH) + const nonce = uuid().substring(0, registrationConstants.NONCE_LENGTH) return createPasswordReset({ driver, nonce, email }) }, resetPassword: async (_parent, { email, nonce, newPassword }, { driver }) => { diff --git a/backend/yarn.lock b/backend/yarn.lock index aefdcf732..c702695e1 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -2061,6 +2061,11 @@ "@types/koa-compose" "*" "@types/node" "*" +"@types/lodash@^4.17.16": + version "4.17.16" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.16.tgz#94ae78fab4a38d73086e962d0b65c30d816bfb0a" + integrity sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g== + "@types/long@^4.0.0": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" diff --git a/webapp/components/Registration/RegistrationSlideInvite.vue b/webapp/components/Registration/RegistrationSlideInvite.vue index 723071510..9041fd345 100644 --- a/webapp/components/Registration/RegistrationSlideInvite.vue +++ b/webapp/components/Registration/RegistrationSlideInvite.vue @@ -23,7 +23,7 @@