Skip to content

Commit

Permalink
Add user featured
Browse files Browse the repository at this point in the history
  • Loading branch information
mczachurski committed Oct 14, 2024
1 parent 3e24a8c commit 5a3f21b
Show file tree
Hide file tree
Showing 33 changed files with 1,036 additions and 155 deletions.
1 change: 1 addition & 0 deletions Sources/VernissageServer/Application+Configure.swift
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ extension Application {
self.migrations.add(User.AddUrl())
self.migrations.add(Exif.AddGpsCoordinates())
self.migrations.add(Exif.AddSoftware())
self.migrations.add(FeaturedUser.CreateFeaturedUsers())

try await self.autoMigrate()
}
Expand Down
13 changes: 4 additions & 9 deletions Sources/VernissageServer/Controllers/InstanceController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,17 +149,12 @@ struct InstanceController {
return nil
}

guard let user = try await User.query(on: request.db).filter(\.$id == contactUserId).first() else {
let usersService = request.application.services.usersService
guard let user = try await usersService.get(on: request.db, id: contactUserId) else {
return nil
}

let baseStoragePath = request.application.services.storageService.getBaseStoragePath(on: request.application)
let baseAddress = request.application.settings.cached?.baseAddress ?? ""

var userDto = UserDto(from: user, baseStoragePath: baseStoragePath, baseAddress: baseAddress)
userDto.email = nil
userDto.locale = nil


let userDto = await usersService.convertToDto(on: request, user: user, flexiFields: user.flexiFields, roles: nil, attachSensitive: false)
return userDto
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,14 @@ struct NotificationsController {

let linkableParams = request.linkableParams()
let notificationsService = request.application.services.notificationsService
let notifications = try await notificationsService.list(on: request.db, for: authorizationPayloadId, linkableParams: linkableParams)

let baseStoragePath = request.application.services.storageService.getBaseStoragePath(on: request.application)
let baseAddress = request.application.settings.cached?.baseAddress ?? ""
let usersService = request.application.services.usersService

let notifications = try await notificationsService.list(on: request.db, for: authorizationPayloadId, linkableParams: linkableParams)

let notificationDtos = await notifications.asyncMap({
let notificationTypeDto = NotificationTypeDto.from($0.notificationType)
let user = UserDto(from: $0.byUser, baseStoragePath: baseStoragePath, baseAddress: baseAddress)

let user = await usersService.convertToDto(on: request, user: $0.byUser, flexiFields: $0.byUser.flexiFields, roles: nil, attachSensitive: false)
let status = await self.getStatus($0.status, on: request)

return NotificationDto(id: $0.stringId(), notificationType: notificationTypeDto, byUser: user, status: status)
Expand Down
9 changes: 2 additions & 7 deletions Sources/VernissageServer/Controllers/RegisterController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -337,13 +337,8 @@ struct RegisterController {
}

private func createNewUserResponse(on request: Request, user: User, flexiFields: [FlexiField]) async throws -> Response {
let baseStoragePath = request.application.services.storageService.getBaseStoragePath(on: request.application)
let baseAddress = request.application.settings.cached?.baseAddress ?? ""

var createdUserDto = UserDto(from: user, flexiFields: flexiFields, baseStoragePath: baseStoragePath, baseAddress: baseAddress)
createdUserDto.email = user.email
createdUserDto.emailWasConfirmed = user.emailWasConfirmed
createdUserDto.locale = user.locale
let usersService = request.application.services.usersService
let createdUserDto = await usersService.convertToDto(on: request, user: user, flexiFields: user.flexiFields, roles: nil, attachSensitive: true)

var headers = HTTPHeaders()
headers.replaceOrAdd(name: .location, value: "/\(UsersController.uri)/@\(user.userName)")
Expand Down
8 changes: 8 additions & 0 deletions Sources/VernissageServer/Controllers/SettingsController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ struct SettingsController {
showLocalTimelineForAnonymous: settings.showLocalTimelineForAnonymous,
showTrendingForAnonymous: settings.showTrendingForAnonymous,
showEditorsChoiceForAnonymous: settings.showEditorsChoiceForAnonymous,
showEditorsUsersChoiceForAnonymous: settings.showEditorsUsersChoiceForAnonymous,
showHashtagsForAnonymous: settings.showHashtagsForAnonymous,
showCategoriesForAnonymous: settings.showCategoriesForAnonymous)

Expand Down Expand Up @@ -526,6 +527,13 @@ struct SettingsController {
transaction: database)
}

if settingsDto.showEditorsUsersChoiceForAnonymous != settings.getBool(.showEditorsUsersChoiceForAnonymous) {
try await self.update(.showEditorsUsersChoiceForAnonymous,
with: .boolean(settingsDto.showEditorsUsersChoiceForAnonymous),
on: request,
transaction: database)
}

if settingsDto.showHashtagsForAnonymous != settings.getBool(.showHashtagsForAnonymous) {
try await self.update(.showHashtagsForAnonymous,
with: .boolean(settingsDto.showHashtagsForAnonymous),
Expand Down
20 changes: 5 additions & 15 deletions Sources/VernissageServer/Controllers/StatusesController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1342,14 +1342,9 @@ struct StatusesController {
let statusesService = request.application.services.statusesService
let linkableUsers = try await statusesService.reblogged(on: request, statusId: statusId, linkableParams: linkableParams)

let baseStoragePath = request.application.services.storageService.getBaseStoragePath(on: request.application)
let baseAddress = request.application.settings.cached?.baseAddress ?? ""

let userProfiles = try await linkableUsers.data.asyncMap { user in
let flexiFields = try await user.$flexiFields.get(on: request.db)
return UserDto(from: user, flexiFields: flexiFields, baseStoragePath: baseStoragePath, baseAddress: baseAddress)
}

let usersService = request.application.services.usersService
let userProfiles = await usersService.convertToDtos(on: request, users: linkableUsers.data, attachSensitive: false)

return LinkableResultDto(
maxId: linkableUsers.maxId,
minId: linkableUsers.minId,
Expand Down Expand Up @@ -1679,13 +1674,8 @@ struct StatusesController {
let statusesService = request.application.services.statusesService
let linkableUsers = try await statusesService.favourited(on: request, statusId: statusId, linkableParams: linkableParams)

let baseStoragePath = request.application.services.storageService.getBaseStoragePath(on: request.application)
let baseAddress = request.application.settings.cached?.baseAddress ?? ""

let userProfiles = try await linkableUsers.data.asyncMap { user in
let flexiFields = try await user.$flexiFields.get(on: request.db)
return UserDto(from: user, flexiFields: flexiFields, baseStoragePath: baseStoragePath, baseAddress: baseAddress)
}
let usersService = request.application.services.usersService
let userProfiles = await usersService.convertToDtos(on: request, users: linkableUsers.data, attachSensitive: false)

return LinkableResultDto(
maxId: linkableUsers.maxId,
Expand Down
116 changes: 107 additions & 9 deletions Sources/VernissageServer/Controllers/TimelinesController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,12 @@ extension TimelinesController: RouteCollection {
.get("hashtag", ":hashtag", use: hashtag)

timelinesGroup
.grouped(EventHandlerMiddleware(.timelinesFeatured))
.get("featured", use: featured)
.grouped(EventHandlerMiddleware(.timelinesFeaturedStatuses))
.get("featured-statuses", use: featuredStatuses)

timelinesGroup
.grouped(EventHandlerMiddleware(.timelinesFeaturedUsers))
.get("featured-users", use: featuredUsers)

timelinesGroup
.grouped(UserPayload.guardMiddleware())
Expand Down Expand Up @@ -474,8 +478,7 @@ struct TimelinesController {

/// Exposing featured timeline. You can set in the settings if the timeline should be visible for anonymous users.
///
/// This is an endpoint that returns a list of statuses that have been
/// to data to a special list of statuses listed by moderators.
/// This is an endpoint that returns a list of statuses that have been featured by moderators/administrators.
///
/// Optional query params:
/// - `onlyLocal` - `true` if list should contain only statuses added on local instance
Expand All @@ -484,12 +487,12 @@ struct TimelinesController {
/// - `sinceId` - return latest entites since entity
/// - `limit` - limit amount of returned entities (default: 40)
///
/// > Important: Endpoint URL: `/api/v1/timelines/featured`.
/// > Important: Endpoint URL: `/api/v1/timelines/featured-statuses`.
///
/// **CURL request:**
///
/// ```bash
/// curl "https://example.com/api/v1/timelines/featured" \
/// curl "https://example.com/api/v1/timelines/featured-statuses" \
/// -X GET \
/// -H "Content-Type: application/json" \
/// -H "Authorization: Bearer [ACCESS_TOKEN]" \
Expand Down Expand Up @@ -585,17 +588,17 @@ struct TimelinesController {
///
/// - Returns: List of linkable statuses.
@Sendable
func featured(request: Request) async throws -> LinkableResultDto<StatusDto> {
func featuredStatuses(request: Request) async throws -> LinkableResultDto<StatusDto> {
let appplicationSettings = request.application.settings.cached
if request.userId == nil && appplicationSettings?.showEditorsChoiceForAnonymous == false {
throw ActionsForbiddenError.editorsChoiceForbidden
throw ActionsForbiddenError.editorsStatusesChoiceForbidden
}

let onlyLocal: Bool = request.query["onlyLocal"] ?? false
let linkableParams = request.linkableParams()

let timelineService = request.application.services.timelineService
let statuses = try await timelineService.featured(on: request.db, linkableParams: linkableParams, onlyLocal: onlyLocal)
let statuses = try await timelineService.featuredStatuses(on: request.db, linkableParams: linkableParams, onlyLocal: onlyLocal)

let statusesService = request.application.services.statusesService
let statusDtos = await statusesService.convertToDtos(on: request, statuses: statuses.data)
Expand All @@ -607,6 +610,101 @@ struct TimelinesController {
)
}

/// Exposing featured users. You can set in the settings if the timeline should be visible for anonymous users.
///
/// This is an endpoint that returns a list of users that have been featured by moderators/administrators.
///
/// Optional query params:
/// - `onlyLocal` - `true` if list should contain only users added on local instance
/// - `minId` - return only newest entities
/// - `maxId` - return only oldest entities
/// - `sinceId` - return latest entites since entity
/// - `limit` - limit amount of returned entities (default: 40)
///
/// > Important: Endpoint URL: `/api/v1/timelines/featured-users`.
///
/// **CURL request:**
///
/// ```bash
/// curl "https://example.com/api/v1/timelines/featured-users" \
/// -X GET \
/// -H "Content-Type: application/json" \
/// -H "Authorization: Bearer [ACCESS_TOKEN]" \
/// ```
///
/// **Example response body:**
///
/// ```json
/// {
/// "data": [
/// {
/// "account": "[email protected]",
/// "activityPubProfile": "https://example.com/users/johndoe",
/// "avatarUrl": "https://example.com/09267580898c4d3abfc5871bbdb4483e.jpeg",
/// "bio": "<p>Landscape, nature and fine-art photographer</p>",
/// "bioHtml": "<p>Landscape, nature and fine-art photographer</p>",
/// "createdAt": "2023-08-16T15:13:08.607Z",
/// "fields": [],
/// "followersCount": 0,
/// "followingCount": 0,
/// "headerUrl": "https://example.com/700049efc6c04068a3634317e1f95e32.jpg",
/// "id": "7267938074834522113",
/// "isLocal": false,
/// "name": "John Doe",
/// "statusesCount": 0,
/// "updatedAt": "2024-02-09T05:12:23.479Z",
/// "userName": "[email protected]"
/// },
/// {
/// "account": "[email protected]",
/// "activityPubProfile": "https://example.com/users/lindadoe",
/// "avatarUrl": "https://example.com/44debf8889d74b5a9be651f575a3651c.jpg",
/// "bio": "<p>Landscape, nature and street photographer</p>",
/// "bioHtml": "<p>Landscape, nature and street photographer</p>",
/// "createdAt": "2024-02-07T10:25:36.538Z",
/// "fields": [],
/// "followersCount": 0,
/// "followingCount": 0,
/// "id": "7332804261530576897",
/// "isLocal": false,
/// "name": "Linda Doe",
/// "statusesCount": 0,
/// "updatedAt": "2024-02-07T10:25:36.538Z",
/// "userName": "[email protected]"
/// }
/// ],
/// "maxId": "7333853122610761729",
/// "minId": "7333853122610761729"
/// }
/// ```
///
/// - Parameters:
/// - request: The Vapor request to the endpoint.
///
/// - Returns: List of linkable users.
@Sendable
func featuredUsers(request: Request) async throws -> LinkableResultDto<UserDto> {
let appplicationSettings = request.application.settings.cached
if request.userId == nil && appplicationSettings?.showEditorsUsersChoiceForAnonymous == false {
throw ActionsForbiddenError.editorsUsersChoiceForbidden
}

let onlyLocal: Bool = request.query["onlyLocal"] ?? false
let linkableParams = request.linkableParams()

let timelineService = request.application.services.timelineService
let users = try await timelineService.featuredUsers(on: request.db, linkableParams: linkableParams, onlyLocal: onlyLocal)

let usersService = request.application.services.usersService
let userDtos = await usersService.convertToDtos(on: request, users: users.data, attachSensitive: false)

return LinkableResultDto(
maxId: users.maxId,
minId: users.minId,
data: userDtos
)
}

/// Exposing home timeline.
///
/// This is the endpoint that is most important to the logged-in user.
Expand Down
9 changes: 3 additions & 6 deletions Sources/VernissageServer/Controllers/TrendingController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -257,14 +257,11 @@ struct TrendingController {
let linkableParams = request.linkableParams()

let trendingService = request.application.services.trendingService
let baseStoragePath = request.application.services.storageService.getBaseStoragePath(on: request.application)
let baseAddress = request.application.settings.cached?.baseAddress ?? ""

let trending = try await trendingService.users(on: request.db, linkableParams: linkableParams, period: period.translate())
let userDtos = await trending.data.asyncMap({
UserDto(from: $0, flexiFields: $0.flexiFields, baseStoragePath: baseStoragePath, baseAddress: baseAddress)
})

let usersService = request.application.services.usersService
let userDtos = await usersService.convertToDtos(on: request, users: trending.data, attachSensitive: false)

return LinkableResultDto(
maxId: trending.maxId,
minId: trending.minId,
Expand Down
Loading

0 comments on commit 5a3f21b

Please sign in to comment.