Add support for etag and not modified resources

This commit is contained in:
Matteo Gheza 2023-10-23 08:08:10 +02:00
parent 14183e390c
commit 235a70160b
10 changed files with 120 additions and 22 deletions

View File

@ -16,10 +16,20 @@ class AlertController extends Controller
public function index() public function index()
{ {
return response()->json( return response()->json(
Alert::with('crew.user') request()->query('full', false) ?
->where('closed', false) Alert::with(['crew.user' => [
->orderBy('created_at', 'desc') "name",
->get() "username",
"chief",
"driver"
]])
->where('closed', false)
->orderBy('created_at', 'desc')
->get()
:
Alert::where('closed', false)
->orderBy('created_at', 'desc')
->get()
); );
} }
@ -113,10 +123,15 @@ class AlertController extends Controller
*/ */
public function show(Request $request, $id) public function show(Request $request, $id)
{ {
User::where('id', $request->user()->id)->update(['last_access' => now()]);
return response()->json( return response()->json(
Alert::with('crew.user')->find($id) Alert::with(['crew.user' => [
"name",
"username",
"chief",
"driver"
]])
->where('id', $id)
->first()
); );
} }

View File

@ -12,6 +12,7 @@
"laravel/framework": "^10.0", "laravel/framework": "^10.0",
"laravel/sanctum": "^3.2", "laravel/sanctum": "^3.2",
"laravel/tinker": "^2.8", "laravel/tinker": "^2.8",
"matthewbdaly/laravel-etag-middleware": "^1.3",
"santigarcor/laratrust": "^7.2", "santigarcor/laratrust": "^7.2",
"sentry/sentry-laravel": "^3.7" "sentry/sentry-laravel": "^3.7"
}, },

57
backend/composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "f099d4ac47fd2d3bbc07fe77412bb9bd", "content-hash": "f8b683b55dd63e861103c15d13fed172",
"packages": [ "packages": [
{ {
"name": "brick/math", "name": "brick/math",
@ -2202,6 +2202,61 @@
], ],
"time": "2023-08-05T12:09:49+00:00" "time": "2023-08-05T12:09:49+00:00"
}, },
{
"name": "matthewbdaly/laravel-etag-middleware",
"version": "1.3.6",
"source": {
"type": "git",
"url": "https://github.com/matthewbdaly/laravel-etag-middleware.git",
"reference": "048cb22f5849f695f134e7e13cb52b4c21ef6fd4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/matthewbdaly/laravel-etag-middleware/zipball/048cb22f5849f695f134e7e13cb52b4c21ef6fd4",
"reference": "048cb22f5849f695f134e7e13cb52b4c21ef6fd4",
"shasum": ""
},
"require": {
"illuminate/http": "^5.5|^6.0|^7.0|^8.0|^9.0|^10.0",
"illuminate/support": "^5.5|^6.0|^7.0|^8.0|^9.0|^10.0"
},
"require-dev": {
"mockery/mockery": "^1.0",
"php-coveralls/php-coveralls": "^2.1",
"phpunit/phpunit": "^4.8|^5.2|^9.0",
"squizlabs/php_codesniffer": "^3.7",
"vimeo/psalm": "^5.8"
},
"type": "library",
"autoload": {
"psr-4": {
"Matthewbdaly\\ETagMiddleware\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Matthew Daly",
"email": "450801+matthewbdaly@users.noreply.github.com"
}
],
"description": "A Laravel middleware for adding ETags to HTTP requests to improve response times",
"keywords": [
"ETags",
"Etag",
"http",
"laravel",
"middleware"
],
"support": {
"issues": "https://github.com/matthewbdaly/laravel-etag-middleware/issues",
"source": "https://github.com/matthewbdaly/laravel-etag-middleware/tree/1.3.6"
},
"time": "2023-03-20T16:12:09+00:00"
},
{ {
"name": "monolog/monolog", "name": "monolog/monolog",
"version": "3.4.0", "version": "3.4.0",

View File

@ -14,6 +14,7 @@ use App\Http\Controllers\ServiceTypeController;
use App\Http\Controllers\TrainingController; use App\Http\Controllers\TrainingController;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Artisan;
use \Matthewbdaly\ETagMiddleware\ETag;
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -44,16 +45,16 @@ Route::middleware('auth:sanctum')->group( function () {
Route::get('/schedules', [ScheduleSlotsController::class, 'index']); Route::get('/schedules', [ScheduleSlotsController::class, 'index']);
Route::post('/schedules', [ScheduleSlotsController::class, 'store']); Route::post('/schedules', [ScheduleSlotsController::class, 'store']);
Route::get('/availability', [AvailabilityController::class, 'get']); Route::get('/availability', [AvailabilityController::class, 'get'])->middleware(ETag::class);
Route::post('/availability', [AvailabilityController::class, 'updateAvailability']); Route::post('/availability', [AvailabilityController::class, 'updateAvailability']);
Route::post('/manual_mode', [AvailabilityController::class, 'updateAvailabilityManualMode']); Route::post('/manual_mode', [AvailabilityController::class, 'updateAvailabilityManualMode']);
Route::get('/alerts', [AlertController::class, 'index']); Route::get('/alerts', [AlertController::class, 'index'])->middleware(ETag::class);
Route::post('/alerts', [AlertController::class, 'store']); Route::post('/alerts', [AlertController::class, 'store']);
Route::get('/alerts/{id}', [AlertController::class, 'show']); Route::get('/alerts/{id}', [AlertController::class, 'show'])->middleware(ETag::class);
Route::patch('/alerts/{id}', [AlertController::class, 'update']); Route::patch('/alerts/{id}', [AlertController::class, 'update']);
Route::get('/services', [ServiceController::class, 'index']); Route::get('/services', [ServiceController::class, 'index'])->middleware(ETag::class);
Route::post('/services', [ServiceController::class, 'createOrUpdate']); Route::post('/services', [ServiceController::class, 'createOrUpdate']);
Route::get('/services/{id}', [ServiceController::class, 'show']); Route::get('/services/{id}', [ServiceController::class, 'show']);
Route::delete('/services/{id}', [ServiceController::class, 'destroy']); Route::delete('/services/{id}', [ServiceController::class, 'destroy']);
@ -63,12 +64,12 @@ Route::middleware('auth:sanctum')->group( function () {
Route::get('/places/search', [PlacesController::class, 'search']); Route::get('/places/search', [PlacesController::class, 'search']);
Route::get('/places/{id}', [PlacesController::class, 'show']); Route::get('/places/{id}', [PlacesController::class, 'show']);
Route::get('/trainings', [TrainingController::class, 'index']); Route::get('/trainings', [TrainingController::class, 'index'])->middleware(ETag::class);
Route::post('/trainings', [TrainingController::class, 'createOrUpdate']); Route::post('/trainings', [TrainingController::class, 'createOrUpdate']);
Route::get('/trainings/{id}', [TrainingController::class, 'show']); Route::get('/trainings/{id}', [TrainingController::class, 'show']);
Route::delete('/trainings/{id}', [TrainingController::class, 'destroy']); Route::delete('/trainings/{id}', [TrainingController::class, 'destroy']);
Route::get('/logs', [LogsController::class, 'index']); Route::get('/logs', [LogsController::class, 'index'])->middleware(ETag::class);
Route::post('/telegram_login_token', [TelegramController::class, 'loginToken']); Route::post('/telegram_login_token', [TelegramController::class, 'loginToken']);

View File

@ -59,7 +59,8 @@
"scripts": [], "scripts": [],
"serviceWorker": true, "serviceWorker": true,
"ngswConfigPath": "ngsw-config.json", "ngswConfigPath": "ngsw-config.json",
"sourceMap": true "sourceMap": true,
"allowedCommonJsDependencies": ["crypto-js"]
}, },
"configurations": { "configurations": {
"production": { "production": {

View File

@ -26,6 +26,8 @@ export class ModalAlertComponent implements OnInit, OnDestroy {
alertClosed = 0; alertClosed = 0;
private etag = "";
constructor( constructor(
public bsModalRef: BsModalRef, public bsModalRef: BsModalRef,
private api: ApiClientService, private api: ApiClientService,
@ -36,7 +38,9 @@ export class ModalAlertComponent implements OnInit, OnDestroy {
loadResponsesData() { loadResponsesData() {
//TODO: do not update data if not changed. Support for content hash in response header? //TODO: do not update data if not changed. Support for content hash in response header?
this.api.get(`alerts/${this.id}`).then((response) => { this.api.get(`alerts/${this.id}`, {}, this.etag).then((response) => {
if(this.api.isLastSame) return;
this.etag = this.api.lastEtag;
console.log(response, this.alertClosed, response.closed); console.log(response, this.alertClosed, response.closed);
if(this.alertClosed !== response.closed) this.alertClosed = response.closed; if(this.alertClosed !== response.closed) this.alertClosed = response.closed;
if(!isEqual(this.crewUsers, response.crew)) this.crewUsers = response.crew; if(!isEqual(this.crewUsers, response.crew)) this.crewUsers = response.crew;

View File

@ -60,6 +60,7 @@ export class TableComponent implements OnInit, OnDestroy {
public data: any = []; public data: any = [];
public displayedData: any = []; public displayedData: any = [];
public originalData: any = []; public originalData: any = [];
private etag: string = "";
public loadDataInterval: NodeJS.Timer | undefined = undefined; public loadDataInterval: NodeJS.Timer | undefined = undefined;
@ -87,7 +88,9 @@ export class TableComponent implements OnInit, OnDestroy {
loadTableData() { loadTableData() {
if(!this.sourceType) this.sourceType = "list"; if(!this.sourceType) this.sourceType = "list";
this.api.get(this.sourceType).then((data: any) => { this.api.get(this.sourceType, {}, this.etag).then((data: any) => {
if(this.api.isLastSame) return;
this.etag = this.api.lastEtag;
this.data = data.filter((row: any) => typeof row.hidden !== 'undefined' ? !row.hidden : true); this.data = data.filter((row: any) => typeof row.hidden !== 'undefined' ? !row.hidden : true);
this.originalData = this.data; this.originalData = this.data;
this.totalElements = this.data.length; this.totalElements = this.data.length;

View File

@ -25,6 +25,8 @@ export class ListComponent implements OnInit, OnDestroy {
public alertLoading = false; public alertLoading = false;
private etag = "";
constructor( constructor(
public api: ApiClientService, public api: ApiClientService,
public auth: AuthService, public auth: AuthService,
@ -35,7 +37,9 @@ export class ListComponent implements OnInit, OnDestroy {
} }
loadAvailability() { loadAvailability() {
this.api.get("availability").then((response) => { this.api.get("availability", {}, this.etag).then((response) => {
if(this.api.isLastSame) return;
this.etag = this.api.lastEtag;
this.available = response.available; this.available = response.available;
this.manual_mode = response.manual_mode; this.manual_mode = response.manual_mode;
}).catch((err) => { }).catch((err) => {

View File

@ -8,6 +8,9 @@ import { Subject } from "rxjs";
export class ApiClientService { export class ApiClientService {
private apiRoot = 'api/'; private apiRoot = 'api/';
public lastEtag = "";
public isLastSame = false;
public alertsChanged = new Subject<void>(); public alertsChanged = new Subject<void>();
public availableUsers: undefined | number = undefined; public availableUsers: undefined | number = undefined;
@ -20,12 +23,20 @@ export class ApiClientService {
return this.apiRoot + endpoint; return this.apiRoot + endpoint;
} }
public get(endpoint: string, data: any = {}) { public get(endpoint: string, data: any = {}, etag: string = "") {
return new Promise<any>((resolve, reject) => { return new Promise<any>((resolve, reject) => {
this.http.get(this.apiEndpoint(endpoint), { this.http.get(this.apiEndpoint(endpoint), {
params: new HttpParams({ fromObject: data }) params: new HttpParams({ fromObject: data }),
observe: 'response',
headers: (etag !== "" && etag !== null) ? {
'If-None-Match': etag
} : {}
}).subscribe({ }).subscribe({
next: (v) => resolve(v), next: (v: any) => {
this.lastEtag = v.headers.get("etag");
this.isLastSame = etag === this.lastEtag;
resolve(v.body);
},
error: (e) => reject(e) error: (e) => reject(e)
}); });
}); });

View File

@ -21,6 +21,7 @@ export class AppComponent {
public loadingRoute = false; public loadingRoute = false;
private loadAlertsInterval: NodeJS.Timer | undefined = undefined; private loadAlertsInterval: NodeJS.Timer | undefined = undefined;
public alerts = []; public alerts = [];
private alertsEtag = "";
constructor( constructor(
public auth: AuthService, public auth: AuthService,
@ -37,8 +38,10 @@ export class AppComponent {
loadAlerts() { loadAlerts() {
if(this.auth.profile) { if(this.auth.profile) {
this.api.get("alerts").then((response) => { this.api.get("alerts", {}, this.alertsEtag).then((response) => {
if(this.api.isLastSame) return;
this.alerts = response; this.alerts = response;
this.alertsEtag = this.api.lastEtag;
}); });
} }
} }