Skip to content

Commit a5521ec

Browse files
committed
Add support for returning transforming activity logs on the front-end
1 parent e15985e commit a5521ec

File tree

9 files changed

+162
-6
lines changed

9 files changed

+162
-6
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Controllers\Api\Client;
4+
5+
use Spatie\QueryBuilder\QueryBuilder;
6+
use Spatie\QueryBuilder\AllowedFilter;
7+
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
8+
use Pterodactyl\Transformers\Api\Client\ActivityLogTransformer;
9+
10+
class ActivityLogController extends ClientApiController
11+
{
12+
/**
13+
* Returns a paginated set of the user's activity logs.
14+
*/
15+
public function __invoke(ClientApiRequest $request): array
16+
{
17+
$activity = QueryBuilder::for($request->user()->activity())
18+
->with('actor')
19+
->allowedFilters([
20+
AllowedFilter::exact('ip'),
21+
AllowedFilter::partial('event'),
22+
])
23+
->paginate(min($request->query('per_page', 50), 100))
24+
->appends($request->query());
25+
26+
return $this->fractal->collection($activity)
27+
->transformWith($this->getTransformer(ActivityLogTransformer::class))
28+
->toArray();
29+
}
30+
}

app/Models/ActivityLog.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
* @property string|null $actor_type
2323
* @property int|null $actor_id
2424
* @property \Illuminate\Support\Collection|null $properties
25-
* @property string $timestamp
25+
* @property \Carbon\Carbon $timestamp
2626
* @property IlluminateModel|\Eloquent $actor
2727
* @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\ActivityLogSubject[] $subjects
2828
* @property int|null $subjects_count
@@ -47,6 +47,8 @@ class ActivityLog extends Model
4747
{
4848
use MassPrunable;
4949

50+
public const RESOURCE_NAME = 'activity_log';
51+
5052
public $timestamps = false;
5153

5254
protected $guarded = [
@@ -56,6 +58,7 @@ class ActivityLog extends Model
5658

5759
protected $casts = [
5860
'properties' => 'collection',
61+
'timestamp' => 'datetime',
5962
];
6063

6164
protected $with = ['subjects'];
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace Pterodactyl\Transformers\Api\Client;
4+
5+
use Pterodactyl\Models\User;
6+
use Pterodactyl\Models\ActivityLog;
7+
8+
class ActivityLogTransformer extends BaseClientTransformer
9+
{
10+
protected array $availableIncludes = ['actor'];
11+
12+
public function getResourceName(): string
13+
{
14+
return ActivityLog::RESOURCE_NAME;
15+
}
16+
17+
public function transform(ActivityLog $model): array
18+
{
19+
return [
20+
'batch' => $model->batch,
21+
'event' => $model->event,
22+
'ip' => $model->ip,
23+
'description' => $model->description,
24+
'properties' => $model->properties,
25+
'timestamp' => $model->timestamp->toIso8601String(),
26+
];
27+
}
28+
29+
public function includeActor(ActivityLog $model)
30+
{
31+
if (!$model->actor instanceof User) {
32+
return $this->null();
33+
}
34+
35+
return $this->item($model->actor, $this->makeTransformer(UserTransformer::class), User::RESOURCE_NAME);
36+
}
37+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { FractalResponseData, FractalResponseList } from '@/api/http';
2+
3+
type Transformer<T> = (callback: FractalResponseData) => T;
4+
5+
const isList = (data: FractalResponseList | FractalResponseData): data is FractalResponseList => data.object === 'list';
6+
7+
function transform<T, M>(data: null | undefined, transformer: Transformer<T>, missing?: M): M;
8+
function transform<T, M>(data: FractalResponseData | null | undefined, transformer: Transformer<T>, missing?: M): T | M;
9+
function transform<T, M>(data: FractalResponseList | null | undefined, transformer: Transformer<T>, missing?: M): T[] | M;
10+
function transform<T> (data: FractalResponseData | FractalResponseList | null | undefined, transformer: Transformer<T>, missing = undefined) {
11+
if (data === undefined || data === null) {
12+
return missing;
13+
}
14+
15+
if (isList(data)) {
16+
return data.data.map(transformer);
17+
}
18+
19+
if (!data || !data.attributes || data.object === 'null_resource') {
20+
return missing;
21+
}
22+
23+
return transformer(data);
24+
}
25+
26+
export { transform };

resources/scripts/api/definitions/index.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { MarkRequired } from 'ts-essentials';
2+
import { FractalResponseData, FractalResponseList } from '../http';
23

34
export type UUID = string;
45

56
// eslint-disable-next-line @typescript-eslint/no-empty-interface
67
export interface Model {}
78

89
interface ModelWithRelationships extends Model {
9-
relationships: Record<string, unknown>;
10+
relationships: Record<string, FractalResponseData | FractalResponseList | undefined>;
1011
}
1112

1213
/**
Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,32 @@
1-
import { Model } from '@/api/definitions';
1+
import { Model, UUID } from '@/api/definitions';
2+
import { SubuserPermission } from '@/state/server/subusers';
3+
4+
interface User extends Model {
5+
uuid: string;
6+
username: string;
7+
email: string;
8+
image: string;
9+
twoFactorEnabled: boolean;
10+
createdAt: Date;
11+
permissions: SubuserPermission[];
12+
can (permission: SubuserPermission): boolean;
13+
}
214

315
interface SSHKey extends Model {
416
name: string;
517
publicKey: string;
618
fingerprint: string;
719
createdAt: Date;
820
}
21+
22+
interface ActivityLog extends Model<'actor'> {
23+
batch: UUID | null;
24+
event: string;
25+
ip: string;
26+
description: string | null;
27+
properties: Record<string, string | unknown>;
28+
timestamp: Date;
29+
relationships: {
30+
actor: User | null;
31+
}
32+
}

resources/scripts/api/definitions/user/transformers.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,47 @@
1-
import { SSHKey } from '@definitions/user/models';
1+
import * as Models from '@definitions/user/models';
2+
import { FractalResponseData } from '@/api/http';
3+
import { transform } from '@definitions/helpers';
24

35
export default class Transformers {
4-
static toSSHKey (data: Record<any, any>): SSHKey {
6+
static toSSHKey (data: Record<any, any>): Models.SSHKey {
57
return {
68
name: data.name,
79
publicKey: data.public_key,
810
fingerprint: data.fingerprint,
911
createdAt: new Date(data.created_at),
1012
};
1113
}
14+
15+
static toUser ({ attributes }: FractalResponseData): Models.User {
16+
return {
17+
uuid: attributes.uuid,
18+
username: attributes.username,
19+
email: attributes.email,
20+
image: attributes.image,
21+
twoFactorEnabled: attributes['2fa_enabled'],
22+
permissions: attributes.permissions || [],
23+
createdAt: new Date(attributes.created_at),
24+
can (permission): boolean {
25+
return this.permissions.includes(permission);
26+
},
27+
};
28+
}
29+
30+
static toActivityLog ({ attributes }: FractalResponseData): Models.ActivityLog {
31+
const { actor } = attributes.relationships || {};
32+
33+
return {
34+
batch: attributes.batch,
35+
event: attributes.event,
36+
ip: attributes.ip,
37+
description: attributes.description,
38+
properties: attributes.properties,
39+
timestamp: new Date(attributes.timestamp),
40+
relationships: {
41+
actor: transform(actor as FractalResponseData, this.toUser, null),
42+
},
43+
};
44+
}
1245
}
1346

1447
export class MetaTransformers {

resources/scripts/api/http.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export interface FractalResponseData {
6868
object: string;
6969
attributes: {
7070
[k: string]: any;
71-
relationships?: Record<string, FractalResponseData | FractalResponseList>;
71+
relationships?: Record<string, FractalResponseData | FractalResponseList | null | undefined>;
7272
};
7373
}
7474

routes/api-client.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
Route::put('/email', [Client\AccountController::class, 'updateEmail'])->name('api:client.account.update-email');
3131
Route::put('/password', [Client\AccountController::class, 'updatePassword'])->name('api:client.account.update-password');
3232

33+
Route::get('/activity', Client\ActivityLogController::class)->name('api:client.account.activity');
34+
3335
Route::get('/api-keys', [Client\ApiKeyController::class, 'index']);
3436
Route::post('/api-keys', [Client\ApiKeyController::class, 'store']);
3537
Route::delete('/api-keys/{identifier}', [Client\ApiKeyController::class, 'delete']);

0 commit comments

Comments
 (0)