diff --git a/prisma/migrations/20240803114906_global_node_naming/migration.sql b/prisma/migrations/20240803114906_global_node_naming/migration.sql new file mode 100644 index 00000000..614c85de --- /dev/null +++ b/prisma/migrations/20240803114906_global_node_naming/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "OrganizationSettings" ADD COLUMN "renameNodeGlobally" BOOLEAN DEFAULT false; + +-- AlterTable +ALTER TABLE "UserOptions" ADD COLUMN "renameNodeGlobally" BOOLEAN DEFAULT false; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 963128ec..6bc638a3 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -182,6 +182,7 @@ model UserOptions { // member table deAuthorizeWarning Boolean? @default(false) addMemberIdAsName Boolean? @default(false) + renameNodeGlobally Boolean? @default(false) } enum AccessLevel { @@ -275,6 +276,9 @@ model Invitation { organizations OrganizationInvitation[] } +// +// ORGANIZATION +// model OrganizationInvitation { id String @id @default(cuid()) invitationId Int @@ -283,10 +287,6 @@ model OrganizationInvitation { organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) } -// -// ORGANIZATION -// - model Organization { id String @id @default(cuid()) createdAt DateTime @default(now()) @@ -359,10 +359,10 @@ model LastReadMessage { } model OrganizationSettings { - id Int @id @default(autoincrement()) - organizationId String @unique - organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) - // Add specific settings fields here + id Int @id @default(autoincrement()) + renameNodeGlobally Boolean? @default(false) + organizationId String @unique + organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) } model MembershipRequest { @@ -379,7 +379,7 @@ model ActivityLog { createdAt DateTime @default(now()) performedById String performedBy User @relation(fields: [performedById], references: [id], onDelete: Cascade) - organizationId String? // Make this optional + organizationId String? organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade) } diff --git a/src/pages/user-settings/network/index.tsx b/src/pages/user-settings/network/index.tsx index efd5c9cf..c1797644 100644 --- a/src/pages/user-settings/network/index.tsx +++ b/src/pages/user-settings/network/index.tsx @@ -77,6 +77,45 @@ const UserNetworkSetting = () => { {t("network.memberTable.memberTableTitle")}
+Enable global node naming
++ When enabled, this feature will: +
+ Note: This feature has priority over "Add Member ID as Name". It applies + only to networks where you are the author and doesn't affect networks + managed by others or organizations. +
+ +
diff --git a/src/server/api/routers/authRouter.ts b/src/server/api/routers/authRouter.ts
index 9767d13f..bb4d0cd1 100644
--- a/src/server/api/routers/authRouter.ts
+++ b/src/server/api/routers/authRouter.ts
@@ -619,6 +619,7 @@ export const authRouter = createTRPCRouter({
showNotationMarkerInTableRow: z.boolean().optional(),
deAuthorizeWarning: z.boolean().optional(),
addMemberIdAsName: z.boolean().optional(),
+ renameNodeGlobally: z.boolean().optional(),
}),
)
.mutation(async ({ ctx, input }) => {
diff --git a/src/server/api/routers/memberRouter.ts b/src/server/api/routers/memberRouter.ts
index 5d603ebc..587ca946 100644
--- a/src/server/api/routers/memberRouter.ts
+++ b/src/server/api/routers/memberRouter.ts
@@ -392,6 +392,74 @@ export const networkMemberRouter = createTRPCRouter({
});
}
+ // get the user options
+ const userOptions = await ctx.prisma.userOptions.findFirst({
+ where: {
+ userId: ctx.session.user.id,
+ },
+ });
+
+ // check if the user wants to update the name globally
+ const shouldUpdateNameGlobally = userOptions?.renameNodeGlobally || false;
+ if (shouldUpdateNameGlobally && input.updateParams.name && !input.organizationId) {
+ // Find all networks where the user is the author
+ const userNetworks = await ctx.prisma.network.findMany({
+ where: {
+ authorId: ctx.session.user.id,
+ organizationId: null, // Only private networks
+ },
+ select: { nwid: true },
+ });
+
+ // Update the node name across all user's private networks
+ await ctx.prisma.network_members.updateMany({
+ where: {
+ id: input.id,
+ nwid: { in: userNetworks.map((network) => network.nwid) },
+ },
+ data: {
+ name: input.updateParams.name,
+ },
+ });
+ }
+ // Check if the organization wants to update the node name globally
+ if (input.organizationId && input.updateParams.name) {
+ // Upsert OrganizationSettings to ensure it exists
+ const organizationOptions = await ctx.prisma.organizationSettings.upsert({
+ where: { organizationId: input.organizationId },
+ update: {},
+ create: { organizationId: input.organizationId },
+ });
+
+ // Check if the organization wants to update the name globally
+ if (organizationOptions.renameNodeGlobally && input.updateParams.name) {
+ // Update node name across all organization networks in a single query
+ await ctx.prisma.network_members.updateMany({
+ where: {
+ id: input.id,
+ nwid_ref: {
+ organizationId: input.organizationId,
+ },
+ },
+ data: {
+ name: input.updateParams.name,
+ },
+ });
+ } else if (input.updateParams.name) {
+ // Update only the specific network member if global renaming is off
+ await ctx.prisma.network_members.updateMany({
+ where: {
+ id: input.id,
+ nwid_ref: {
+ organizationId: input.organizationId,
+ },
+ },
+ data: {
+ name: input.updateParams.name,
+ },
+ });
+ }
+ }
// if users click the re-generate icon on IP address
const response = await ctx.prisma.network.update({
where: {
diff --git a/src/server/api/services/memberService.ts b/src/server/api/services/memberService.ts
index 7ecbe94d..c0f6ba65 100644
--- a/src/server/api/services/memberService.ts
+++ b/src/server/api/services/memberService.ts
@@ -120,35 +120,49 @@ const findActivePreferredPeerPath = (peers: Peers | null) => {
* @returns A promise that resolves to the created network member.
*/
const addNetworkMember = async (ctx, member: MemberEntity) => {
- const user = await prisma.user.findFirst({
- where: {
- id: ctx.session.user.id,
- },
- select: {
- options: true,
- },
- });
+ // 1. get the user options
+ // 2. check if the new member is joining a organization network
+ const [user, memberOfOrganization] = await Promise.all([
+ prisma.user.findUnique({
+ where: { id: ctx.session.user.id },
+ select: { options: true, network: { select: { nwid: true } } },
+ }),
+ prisma.network.findFirst({
+ where: { nwid: member.nwid },
+ select: { organizationId: true, organization: { select: { settings: true } } },
+ }),
+ ]);
- const memberData = {
- id: member.id,
- lastSeen: new Date(),
- creationTime: new Date(),
- name: user.options?.addMemberIdAsName ? member.id : null,
+ const findNamedMember = async ({ orgId }: { orgId: string }) => {
+ return await prisma.network_members.findFirst({
+ where: {
+ id: member.id,
+ name: { not: null },
+ nwid_ref: {
+ organizationId: orgId,
+ authorId: orgId ? null : ctx.session.user.id,
+ },
+ },
+ select: { name: true },
+ });
};
- // check if the new member is joining a organization network
- const org = await prisma.network.findFirst({
- where: { nwid: member.nwid },
- select: { organizationId: true },
- });
+ let name = null;
// send webhook if the new member is joining a organization network
- if (org) {
+ if (memberOfOrganization) {
+ // check if global organization member naming is enabled, and if so find the first available name
+ if (memberOfOrganization.organization?.settings?.renameNodeGlobally) {
+ const namedOrgMember = await findNamedMember({
+ orgId: memberOfOrganization.organizationId,
+ });
+ name = namedOrgMember?.name;
+ }
try {
// Send webhook
await sendWebhook