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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/api/src/modules/identity/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export class AuthController {
password: body.password,
firstName: body.firstName,
lastName: body.lastName,
role: 'user',
role: 'member',
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('InvitationsController', () => {
it('should call service.create', async () => {
const dto: CreateInvitation = {
email: 'test@example.com',
role: 'user',
role: 'member',
organizationId: 'org-123',
};
const req = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('InvitationsService', () => {
it('should call authProvider.createInvitation', async () => {
const dto = {
email: 'test@example.com',
role: 'user',
role: 'member',
organizationId: 'org-123',
};
await service.create(dto, 'user-123');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe('InvitationsValidation', () => {
it('should validate valid invitation request', () => {
const valid = {
email: 'test@example.com',
role: 'user',
role: 'member',
organizationId: 'org-123',
};
expect(CreateInvitationSchema.safeParse(valid).success).toBe(true);
Expand All @@ -17,7 +17,7 @@ describe('InvitationsValidation', () => {
it('should require valid email', () => {
const invalid = {
email: 'invalid',
role: 'user',
role: 'member',
organizationId: 'org-123',
};
expect(CreateInvitationSchema.safeParse(invalid).success).toBe(false);
Expand All @@ -26,7 +26,7 @@ describe('InvitationsValidation', () => {
it('should allow optional organizationId', () => {
const validWithoutOrg = {
email: 'test@example.com',
role: 'user',
role: 'member',
};
expect(CreateInvitationSchema.safeParse(validWithoutOrg).success).toBe(
true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ describe('SystemAdminController', () => {
controller.createUser({
name: 'Test',
email: 'taken@example.com',
role: 'user',
role: 'member',
}),
).rejects.toThrow(BadRequestException);
});
Expand All @@ -201,14 +201,14 @@ describe('SystemAdminController', () => {
const result = await controller.createUser({
name: 'Test',
email: 'new@example.com',
role: 'user',
role: 'member',
});

expect(result).toEqual(mockUser);
expect(mockUserProvider.create).toHaveBeenCalledWith({
name: 'Test',
email: 'new@example.com',
role: 'user',
role: 'member',
});
expect(mockUserProvider.update).not.toHaveBeenCalled();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe('UsersController', () => {
it('should call userProvider.create with correct parameters', async () => {
const createUser: CreateUser = {
email: 'test@example.com',
role: 'user',
role: 'member',
};
const result = { id: '1', ...createUser };
userProvider.create.mockResolvedValue(result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ describe('Users Validation', () => {
const result = CreateUserSchema.safeParse(user);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.role).toBe('user');
expect(result.data.role).toBe('member');
}
});
});
Expand Down
6 changes: 3 additions & 3 deletions apps/api/src/modules/identity/users/users.validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const ROLES = {
ADMIN: 'admin',
EDITOR: 'editor',
VIEWER: 'viewer',
USER: 'user',
MEMBER: 'member',
} as const;

/**
Expand Down Expand Up @@ -36,7 +36,7 @@ export const CreateUserSchema = z.object({
.string()
.min(2, { message: 'Company name is required' })
.optional(), // Optional on CreateUser (e.g. invites), required on Signup
role: RoleEnum.optional().default('user'),
role: RoleEnum.optional().default('member'),
password: z
.string()
.min(8, { message: 'Password must be at least 8 characters' })
Expand Down Expand Up @@ -74,7 +74,7 @@ export class CompleteInvite extends createZodDto(CompleteInviteSchema) {}

export const InviteUserSchema = z.object({
email: z.string().email(),
role: RoleEnum.default('user'),
role: RoleEnum.default('member'),
});

export class InviteUser extends createZodDto(InviteUserSchema) {}
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export const UserShow = ({
</div>
<div className="space-y-1">
<p className="text-sm font-medium text-muted-foreground">Role</p>
<Badge variant="secondary">{record?.role || "user"}</Badge>
<Badge variant="secondary">{record?.role || "member"}</Badge>
</div>
<div className="space-y-1">
<p className="text-sm font-medium text-muted-foreground">Organization ID</p>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/modules/identity/users/InviteUserDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export function InviteUserDialog({ resource = "users" }: InviteUserDialogProps)
useEffect(() => {
if (open && roles.length > 0) {
// Default to 'member' or first available
const def = (roles.find(r => r.id === 'user' || r.id === 'member')?.id as string) || (roles[0]?.id as string);
const def = (roles.find(r => r.id === 'member')?.id as string) || (roles[0]?.id as string);
if (def && !form.getValues('role')) {
form.setValue('role', def);
}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/test/auth-provider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ describe('authProvider', () => {

it('should return access denied error when user is not admin', async () => {
(authClient.getSession as unknown as Mock).mockResolvedValue({
data: { user: { id: '123', role: 'user' } },
data: { user: { id: '123', role: 'member' } },
error: null,
});

Expand Down
8 changes: 8 additions & 0 deletions packages/identity/drizzle/0002_update_role_default.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- Migration: Update user role default from 'user' to 'member'
-- Aligns schema with unified role naming system

-- Step 1: Backfill existing rows (update any 'user' roles to 'member')
UPDATE "user" SET "role" = 'member' WHERE "role" = 'user';

-- Step 2: Set new default for future inserts
ALTER TABLE "user" ALTER COLUMN "role" SET DEFAULT 'member';
Comment thread
coderabbitai[bot] marked this conversation as resolved.
12 changes: 6 additions & 6 deletions packages/identity/src/adapters/better-auth.adapter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ describe("BetterAuthAdapter", () => {
image: null,
createdAt: new Date(),
updatedAt: new Date(),
role: "user",
role: "member",
systemRole: null,
banned: null,
banReason: null,
Expand Down Expand Up @@ -223,7 +223,7 @@ describe("BetterAuthAdapter", () => {
image: null,
createdAt: new Date(),
updatedAt: new Date(),
role: "user",
role: "member",
systemRole: null,
banned: null,
banReason: null,
Expand Down Expand Up @@ -355,7 +355,7 @@ describe("BetterAuthAdapter", () => {
image: null,
createdAt: new Date(),
updatedAt: new Date(),
role: "user",
role: "member",
systemRole: null,
banned: null,
banReason: null,
Expand Down Expand Up @@ -388,7 +388,7 @@ describe("BetterAuthAdapter", () => {
image: null,
createdAt: new Date(),
updatedAt: new Date(),
role: "user",
role: "member",
systemRole: null,
banned: null,
banReason: null,
Expand Down Expand Up @@ -468,7 +468,7 @@ describe("BetterAuthAdapter", () => {
invitation: {
id: "x",
email: "x@y.com",
role: "user",
role: "member",
organizationId: "o1",
inviterId: "u1",
status: "pending",
Expand Down Expand Up @@ -509,7 +509,7 @@ describe("BetterAuthAdapter", () => {
const inv = mkInv();
db.query.invitation.findFirst.mockResolvedValueOnce(inv);
const gi = await adapter.getInvitation("i1");
expect(gi?.role).toBe("user");
expect(gi?.role).toBe("member");

// Test unknown status fallback to pending
const weirdInv = mkInv({ status: "weird" });
Expand Down
6 changes: 3 additions & 3 deletions packages/identity/src/adapters/better-auth.adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,7 @@ export class BetterAuthAdapter implements IAuthProvider {
id: uuidv4(),
organizationId: inv.organizationId,
userId: userId,
role: inv.role === "user" ? "member" : inv.role || "member",
role: inv.role || "member",
createdAt: new Date(),
});
} else {
Expand Down Expand Up @@ -715,7 +715,7 @@ export class BetterAuthAdapter implements IAuthProvider {
})[];
},
): Promise<UserInterface> {
const role = dbUser.role || "user";
const role = dbUser.role || "member";
const permissions: Set<string> = new Set();
const roleIds: string[] = [];

Expand Down Expand Up @@ -900,7 +900,7 @@ export class BetterAuthAdapter implements IAuthProvider {
return {
id: dbInv.id,
email: dbInv.email,
role: dbInv.role || "user",
role: dbInv.role || "member",
organizationId: dbInv.organizationId,
inviterId: dbInv.inviterId,
status: status,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ const mkUser = (over?: Partial<User>): User => ({
image: undefined,
createdAt: new Date(),
updatedAt: new Date(),
role: "user",
role: "member",
banned: false,
banReason: null,
banExpires: null,
Expand Down
4 changes: 2 additions & 2 deletions packages/identity/src/adapters/drizzle-user.adapter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ const mkAuth = (over?: Partial<IAuthProvider>): IAuthProvider =>
image: undefined,
createdAt: new Date(),
updatedAt: new Date(),
role: "user",
role: "member",
banned: false,
banReason: null,
banExpires: null,
Expand All @@ -102,7 +102,7 @@ const mkUser = (over?: Partial<schema.User>): schema.User =>
image: null,
createdAt: new Date(),
updatedAt: new Date(),
role: "user",
role: "member",
banned: false,
banReason: null,
banExpires: null,
Expand Down
1 change: 0 additions & 1 deletion packages/identity/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export enum Role {
Owner = "owner",
Admin = "admin",
Member = "member",
User = "user",
}

export type PermissionType = (typeof ALL_PERMISSIONS)[number];
Expand Down
2 changes: 1 addition & 1 deletion packages/identity/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const user = pgTable("user", {
image: text("image"),
createdAt: timestamp("createdAt").notNull(),
updatedAt: timestamp("updatedAt").notNull(),
role: text("role").default("user"),
role: text("role").default("member"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for migration files in common locations
echo "=== Looking for migration files ==="
find . -type f \( -name "*migration*" -o -name "*migrate*" \) | head -30

echo ""
echo "=== Looking for SQL files in drizzle or migrations directories ==="
find . -type d \( -name "*migration*" -o -name "*migrate*" -o -name "*drizzle*" \) 2>/dev/null

echo ""
echo "=== Searching for SQL files ==="
fd --type f '\.sql$' | head -30

echo ""
echo "=== Searching for files referencing role default or member ==="
rg "role.*member|member.*role" --type sql 2>/dev/null | head -20

Repository: pramodnarayana/nexiom

Length of output: 914


🏁 Script executed:

#!/bin/bash
# Check the migration files in packages/identity/drizzle
echo "=== Migration files in packages/identity/drizzle/ ==="
ls -la packages/identity/drizzle/

echo ""
echo "=== Content of 0000_sudden_silver_surfer.sql ==="
cat packages/identity/drizzle/0000_sudden_silver_surfer.sql

echo ""
echo "=== Content of 0001_workable_santa_claus.sql ==="
cat packages/identity/drizzle/0001_workable_santa_claus.sql

Repository: pramodnarayana/nexiom

Length of output: 2345


🏁 Script executed:

#!/bin/bash
# Read the schema.ts file around line 23
echo "=== Content of packages/identity/src/schema.ts around line 23 ==="
cat -n packages/identity/src/schema.ts | sed -n '1,50p'

Repository: pramodnarayana/nexiom

Length of output: 2060


🏁 Script executed:

#!/bin/bash
# Check the drizzle meta directory for metadata about schema changes
echo "=== Content of packages/identity/drizzle/meta ==="
ls -la packages/identity/drizzle/meta/

echo ""
echo "=== Check git status for schema changes ==="
cd packages/identity && git diff src/schema.ts 2>/dev/null || echo "Git diff not available"

echo ""
echo "=== Check if schema file was recently modified ==="
cd packages/identity && git log --oneline -5 -- src/schema.ts 2>/dev/null || echo "Git log not available"

echo ""
echo "=== Check migration meta files ==="
cat packages/identity/drizzle/meta/_journal.json 2>/dev/null || echo "Journal file not found"

Repository: pramodnarayana/nexiom

Length of output: 694


🏁 Script executed:

#!/bin/bash
# Read the journal file directly
echo "=== Content of packages/identity/drizzle/meta/_journal.json ==="
cat packages/identity/drizzle/meta/_journal.json

echo ""
echo "=== Check if latest snapshot (0001) contains role default ==="
grep -A 2 -B 2 '"role"' packages/identity/drizzle/meta/0001_snapshot.json | head -20

Repository: pramodnarayana/nexiom

Length of output: 1000


🏁 Script executed:

#!/bin/bash
# Extract and format the user table definition from the latest snapshot
echo "=== User table definition from 0001_snapshot.json ==="
python3 << 'PYTHON_EOF'
import json

with open('packages/identity/drizzle/meta/0001_snapshot.json', 'r') as f:
    snapshot = json.load(f)

# Find the user table
if 'public.user' in snapshot['schema']['tables']:
    user_table = snapshot['schema']['tables']['public.user']
    print(json.dumps(user_table, indent=2))
else:
    print("User table not found in snapshot")
PYTHON_EOF

Repository: pramodnarayana/nexiom

Length of output: 227


🏁 Script executed:

#!/bin/bash
# First, let's see the actual structure of the snapshot
echo "=== Structure of 0001_snapshot.json ==="
python3 << 'PYTHON_EOF'
import json

with open('packages/identity/drizzle/meta/0001_snapshot.json', 'r') as f:
    snapshot = json.load(f)

# Print top-level keys
print("Top-level keys:", list(snapshot.keys()))
print()

# Look for user table
for key in snapshot.keys():
    if 'user' in str(snapshot[key]).lower() and isinstance(snapshot[key], dict):
        print(f"Found 'user' in key: {key}")
        
# Try to find the user table definition
tables = snapshot.get('tables', {})
if not tables:
    # Try other possible keys
    for key in snapshot:
        if isinstance(snapshot[key], dict) and any('user' in str(k).lower() for k in snapshot[key].keys()):
            tables = snapshot[key]
            print(f"Found tables under key: {key}")
            break

# Search for user table
for table_name, table_def in tables.items():
    if 'user' in table_name.lower():
        print(f"\nUser table: {table_name}")
        if 'columns' in table_def:
            for col_name, col_def in table_def['columns'].items():
                if col_name == 'role':
                    print(f"  {col_name}: {col_def}")
PYTHON_EOF

Repository: pramodnarayana/nexiom

Length of output: 396


A database migration is required for the role default change. The schema.ts file has been updated to set the user role default to "member", but the latest migration snapshot (0001_workable_santa_claus) still reflects the previous default of "user".

Generate a new Drizzle migration to alter the user table and update the role column default value from "user" to "member", then apply it to the database.

🤖 Prompt for AI Agents
In `@packages/identity/src/schema.ts` at line 23, The schema change set role:
text("role").default("member") requires a matching Drizzle migration because the
existing migration snapshot 0001_workable_santa_claus still sets the default to
"user"; create a new migration that alters the users table to change the role
column default from 'user' to 'member' (e.g., ALTER TABLE users ALTER COLUMN
role SET DEFAULT 'member' for Postgres or the DB-specific equivalent), add the
migration file to your migrations folder and run the Drizzle migration command
to apply it to the database so the schema and migration history stay in sync.

banned: boolean("banned"),
banReason: text("banReason"),
banExpires: timestamp("banExpires"),
Expand Down