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
51 changes: 50 additions & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,23 @@ model User {
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")

// Advanced profile fields
bio String?
location String?
avatarUrl String? @map("avatar_url")

// Preferences and privacy
preferences Json?
privacySettings Json? @map("privacy_settings")
exportRequestedAt DateTime? @map("export_requested_at")

// Relationships
followers UserRelationship[] @relation("Followers")
following UserRelationship[] @relation("Following")

// Activity
activities UserActivity[]

properties Property[]
receivedTransactions Transaction[] @relation("UserTransactions")
userRole Role? @relation(fields: [roleId], references: [id], onDelete: SetNull)
Expand All @@ -36,9 +53,41 @@ model User {
@@index([walletAddress]) // For searching users by wallet address
@@index([role]) // For filtering users by role
@@index([createdAt]) // For sorting/filtering by creation date
// Consider adding an index on isVerified if you often filter by verification status
@@index([isVerified])
@@index([location])
@@map("users")
// User activity tracking
model UserActivity {
id String @id @default(cuid())
userId String @map("user_id")
action String
metadata Json?
createdAt DateTime @default(now()) @map("created_at")

user User @relation(fields: [userId], references: [id], onDelete: Cascade)

@@index([userId])
@@index([action])
@@index([createdAt])
@@map("user_activities")
}

// User relationship (followers, connections)
model UserRelationship {
id String @id @default(cuid())
followerId String @map("follower_id")
followingId String @map("following_id")
status String @default("active")
createdAt DateTime @default(now()) @map("created_at")

follower User @relation("Followers", fields: [followerId], references: [id], onDelete: Cascade)
following User @relation("Following", fields: [followingId], references: [id], onDelete: Cascade)

@@unique([followerId, followingId])
@@index([followerId])
@@index([followingId])
@@map("user_relationships")
}
}

model Property {
Expand Down
36 changes: 36 additions & 0 deletions src/models/user.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,46 @@ export class User implements PrismaUser {

createdAt: Date;
updatedAt: Date;

// Advanced profile fields
bio?: string | null;
location?: string | null;
avatarUrl?: string | null;

// Preferences and privacy
preferences?: any | null;
privacySettings?: any | null;
exportRequestedAt?: Date | null;

// Relationships
followers?: UserRelationship[];
following?: UserRelationship[];

// Activity
activities?: UserActivity[];
}

/**
* Input used when creating a user
// User activity entity
export class UserActivity {
id: string;
userId: string;
action: string;
metadata?: any;
createdAt: Date;
}

// User relationship entity
export class UserRelationship {
id: string;
followerId: string;
followingId: string;
status: string;
createdAt: Date;
follower?: User;
following?: User;
}
* Flexible enough for email/password and Web3 users
*/
export type CreateUserInput = {
Expand Down
44 changes: 44 additions & 0 deletions src/users/dto/create-user.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,48 @@ export class CreateUserDto {
@IsXssSafe({ message: 'Wallet address contains potentially malicious content' })
@IsNotSqlInjection({ message: 'Wallet address contains potential SQL injection' })
walletAddress?: string;

@ApiPropertyOptional({
description: 'User biography',
example: 'Blockchain enthusiast and property investor.',
maxLength: 500,
})
@IsOptional()
@IsString()
@MaxLength(500)
bio?: string;

@ApiPropertyOptional({
description: 'User location',
example: 'London, UK',
maxLength: 100,
})
@IsOptional()
@IsString()
@MaxLength(100)
location?: string;

@ApiPropertyOptional({
description: 'Avatar image URL',
example: 'https://example.com/avatar.jpg',
})
@IsOptional()
@IsString()
avatarUrl?: string;

@ApiPropertyOptional({
description: 'User preferences (JSON object)',
example: '{ "theme": "dark", "notifications": true }',
type: Object,
})
@IsOptional()
preferences?: any;

@ApiPropertyOptional({
description: 'User privacy settings (JSON object)',
example: '{ "profileVisible": true }',
type: Object,
})
@IsOptional()
privacySettings?: any;
}
56 changes: 56 additions & 0 deletions src/users/dto/user-response.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,62 @@ export class UserResponseDto {
})
walletAddress?: string;

@ApiPropertyOptional({
description: 'User biography',
example: 'Blockchain enthusiast and property investor.',
})
bio?: string;

@ApiPropertyOptional({
description: 'User location',
example: 'London, UK',
})
location?: string;

@ApiPropertyOptional({
description: 'Avatar image URL',
example: 'https://example.com/avatar.jpg',
})
avatarUrl?: string;

@ApiPropertyOptional({
description: 'User preferences (JSON object)',
example: '{ "theme": "dark", "notifications": true }',
type: Object,
})
preferences?: any;

@ApiPropertyOptional({
description: 'User privacy settings (JSON object)',
example: '{ "profileVisible": true }',
type: Object,
})
privacySettings?: any;

@ApiPropertyOptional({
description: 'Followers count',
example: 10,
})
followersCount?: number;

@ApiPropertyOptional({
description: 'Following count',
example: 5,
})
followingCount?: number;

@ApiPropertyOptional({
description: 'User activity count',
example: 100,
})
activityCount?: number;

@ApiPropertyOptional({
description: 'User login count',
example: 20,
})
loginCount?: number;

@ApiProperty({
description: 'Whether the user email is verified',
example: true,
Expand Down
80 changes: 80 additions & 0 deletions src/users/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,84 @@ export class UserController {
findOne(@Param('id') id: string) {
return this.userService.findById(id);
}

// --- Advanced Features ---

@Patch(':id/profile')
@ApiOperation({ summary: 'Update user profile (bio, location, avatar)' })
updateProfile(@Param('id') id: string, @Body() profile: { bio?: string; location?: string; avatarUrl?: string }) {
return this.userService.updateProfile(id, profile);
}

@Patch(':id/preferences')
@ApiOperation({ summary: 'Update user preferences' })
updatePreferences(@Param('id') id: string, @Body() preferences: any) {
return this.userService.updatePreferences(id, preferences);
}

@Post(':id/activity')
@ApiOperation({ summary: 'Log user activity' })
logActivity(@Param('id') id: string, @Body() body: { action: string; metadata?: any }) {
return this.userService.logActivity(id, body.action, body.metadata);
}

@Get(':id/activity')
@ApiOperation({ summary: 'Get user activity history' })
getActivityHistory(@Param('id') id: string) {
return this.userService.getActivityHistory(id);
}

@Patch(':id/avatar')
@ApiOperation({ summary: 'Update user avatar' })
updateAvatar(@Param('id') id: string, @Body() body: { avatarUrl: string }) {
return this.userService.updateAvatar(id, body.avatarUrl);
}

@Get('search/:query')
@ApiOperation({ summary: 'Search users by name, email, or location' })
searchUsers(@Param('query') query: string) {
return this.userService.searchUsers(query);
}

@Post(':id/follow/:targetId')
@ApiOperation({ summary: 'Follow another user' })
followUser(@Param('id') id: string, @Param('targetId') targetId: string) {
return this.userService.followUser(id, targetId);
}

@Delete(':id/follow/:targetId')
@ApiOperation({ summary: 'Unfollow a user' })
unfollowUser(@Param('id') id: string, @Param('targetId') targetId: string) {
return this.userService.unfollowUser(id, targetId);
}

@Get(':id/followers')
@ApiOperation({ summary: 'List followers of a user' })
getFollowers(@Param('id') id: string) {
return this.userService.getFollowers(id);
}

@Get(':id/following')
@ApiOperation({ summary: 'List users a user is following' })
getFollowing(@Param('id') id: string) {
return this.userService.getFollowing(id);
}

@Get(':id/analytics')
@ApiOperation({ summary: 'Get user analytics and engagement metrics' })
getUserAnalytics(@Param('id') id: string) {
return this.userService.getUserAnalytics(id);
}

@Patch(':id/privacy')
@ApiOperation({ summary: 'Update user privacy settings' })
updatePrivacySettings(@Param('id') id: string, @Body() privacySettings: any) {
return this.userService.updatePrivacySettings(id, privacySettings);
}

@Post(':id/export')
@ApiOperation({ summary: 'Request user data export' })
requestDataExport(@Param('id') id: string) {
return this.userService.requestDataExport(id);
}
}
Loading
Loading