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
409 changes: 409 additions & 0 deletions .claude/skills/add-api-caller/SKILL.md

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions .claude/skills/add-model/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ Where `<module>` is the target sub-library directory name (e.g. `locator`, `asse

## Workflow

### 0. Enter plan mode

Use the `EnterPlanMode` tool immediately before doing any work. Present the full plan to the user
and wait for approval via `ExitPlanMode` before writing any files or running any commands.

### 1. Collect the backend struct/schema

Ask the user to paste or describe the backend definition (Python ObjectType, GraphQL schema,
Expand Down Expand Up @@ -146,6 +151,13 @@ make freezed

List the created and modified files and whether the build succeeded.

If the model needs GraphQL API caller methods (`fetch`, `fetchAll`, `save`, `delete`/`expire`),
suggest the user run:

```
/add-api-caller <module> <ModelName>
```

## Rules

- Never manually edit `.freezed.dart` or `.g.dart` files.
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## 3.7.0

- Added `unknown`, `userNotFound`, `wrongPassword`, `accountBlocked`, and `passwordUsedBefore` values to `ApiStatus`.
- Removed deprecated `telegramUnauthorized` and `telegramBadRequest` values from `ApiStatus`.
- Added `MapLayerInput` model with full API callers (`fetch`, `fetchAll`, `save`, `delete`).
- Removed `LocatorApiResponse` in favour of the generic `ApiResponse<Locator, Map<String, dynamic>>`.
- Removed `PoiApiResponse` in favour of the generic `ApiResponse<Poi, Map<String, dynamic>>`.
- Replaced hardcoded `'INTERNAL_ERROR'` strings with `ApiStatus.internalError.toJson()` across all API callers.

## 3.6.29

- Updated `Locator.fetchAllGraphqlQuery` to include `description` in the query result fields.
Expand Down
18 changes: 10 additions & 8 deletions lib/src/access/src/access_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ abstract class AccessInput with _$AccessInput {

final data = response.data;
if (data == null) {
onResponse?.call('INTERNAL_ERROR');
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/AccessInput/save(): No response from server");
return false;
}
Expand All @@ -64,13 +64,14 @@ abstract class AccessInput with _$AccessInput {
? (useUuid ? data['data']['addAccessPermissionUuid'] : data['data']['addAccessPermission'])
: (useUuid ? data['data']['editAccessPermissionUuid'] : data['data']['editAccessPermission']);
if (result == null) {
onResponse?.call('INTERNAL_ERROR');
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/AccessInput/save(): No result from server");
return false;
}

if (result['status'] != 'OK') {
onResponse?.call(result['status']);
final status = ApiStatus.fromJson(result['status']);
if (status != ApiStatus.ok) {
onResponse?.call(status.toJson());
return false;
}

Expand Down Expand Up @@ -106,20 +107,21 @@ abstract class AccessInput with _$AccessInput {

final data = response.data;
if (data == null) {
onResponse?.call('INTERNAL_ERROR');
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/Access/delete(): No response from server");
return false;
}

final result = data['data'][useUuid ? 'deleteAccessPermissionUuid' : 'deleteAccessPermission'];
if (result == null) {
onResponse?.call('INTERNAL_ERROR');
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/Access/delete(): No result from server");
return false;
}

if (result['status'] != 'OK') {
onResponse?.call(result['status']);
final status = ApiStatus.fromJson(result['status']);
if (status != ApiStatus.ok) {
onResponse?.call(status.toJson());
return false;
}

Expand Down
10 changes: 5 additions & 5 deletions lib/src/api/api.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 23 additions & 26 deletions lib/src/api/src/status.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ part of '../api.dart';

@JsonEnum(alwaysCreate: true)
enum ApiStatus {
/// [unknown] - Unknown or unrecognized status. Used as the default fallback.
@JsonValue('UNKNOWN')
unknown,

/// [ok] - The request was successful.
@JsonValue('OK')
ok,
Expand All @@ -10,6 +14,18 @@ enum ApiStatus {
@JsonValue('NOTFOUND')
notfound,

/// [userNotFound] - The user was not found, please check the credentials and try again.
@JsonValue('USER_NOT_FOUND')
userNotFound,

/// [wrongPassword] - The submitted password is incorrect. Please check your credentials and try again.
@JsonValue('WRONG_PASSWORD')
wrongPassword,

/// [accountBlocked] - Your account has been blocked due to too many wrong password attempts.
@JsonValue('ACCOUNT_BLOCKED')
accountBlocked,

/// [internalError] - Internal server error, please try again later. If the problem persists,
/// please contact us through support@layrz.com. (English or Spanish support)
@JsonValue('INTERNALERROR')
Expand Down Expand Up @@ -56,29 +72,6 @@ enum ApiStatus {
@JsonValue('LIMITREACHED')
limitReached,

/// [telegramUnauthorized] - We are sorry, but the request for telegram hooks failed, please verify the bot
/// token and chat id sended in the request. The 401 means that the token is invalid.
@JsonValue('TELEGRAMUNAUTHORIZED')
telegramUnauthorized,

/// [telegramBadRequest] - We are sorry, but the request for telegram hooks failed, please verify
/// the bot token and chat id sended in the request. The 400 means that the chat id provide is invalid.
@JsonValue('TELEGRAMBADREQUEST')
telegramBadRequest,

/// [malformedPlan] - The provided plan data is malformed or invalid.
@JsonValue('MALFORMEDPLAN')
malformedPlan,

/// [subscriptionAlreadyAdded] - Means the subscription what you want to add is already added previously.
@JsonValue('SUBSCRIPTIONALREADYADDED')
subscriptionAlreadyAdded,

/// [malformedSubscription] - Means the subscription item cannot be saved because it has more items in the
/// ecosystem than the quantity limit.
@JsonValue('MALFORMEDSUBSCRIPTION')
malformedSubscription,

/// [fileNotFound] - The file was not found in our storage server.
@JsonValue('FILE_NOT_FOUND')
fileNotFound,
Expand Down Expand Up @@ -169,19 +162,23 @@ enum ApiStatus {

/// [gptDisabled] - Layo AI is disabled right now, please try again later
@JsonValue('GPT_DISABLED')
gptDisabled;
gptDisabled,

/// [passwordUsedBefore] - The submitted password was used before, please choose a different password.
@JsonValue('PASSWORD_USED_BEFORE')
passwordUsedBefore;

@override
String toString() => toJson();

/// [toJson] - Converts an [ApiStatus] to a JSON string.
String toJson() => _$ApiStatusEnumMap[this] ?? 'INTERNALERROR';
String toJson() => _$ApiStatusEnumMap[this] ?? 'UNKNOWN';

/// [fromJson] - Converts a JSON string to an [ApiStatus].
static ApiStatus fromJson(String json) => _$ApiStatusEnumMap.entries
.firstWhere(
(element) => element.value == json,
orElse: () => const MapEntry(ApiStatus.internalError, 'INTERNALERROR'),
orElse: () => const MapEntry(ApiStatus.unknown, 'UNKNOWN'),
)
.key;
}
9 changes: 5 additions & 4 deletions lib/src/app/src/registered_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,21 @@ abstract class RegisteredApp with _$RegisteredApp {

final data = response.data;
if (data == null) {
onResponse?.call('INTERNAL_ERROR');
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/RegisteredApp/fetchAll(): No response from server");
return [];
}

final result = data['data']['registeredApps'];
if (result == null) {
onResponse?.call('INTERNAL_ERROR');
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/RegisteredApp/fetchAll(): No result from server");
return [];
}

if (result['status'] != 'OK') {
onResponse?.call(result['status']);
final status = ApiStatus.fromJson(result['status']);
if (status != ApiStatus.ok) {
onResponse?.call(status.toJson());
return [];
}

Expand Down
6 changes: 1 addition & 5 deletions lib/src/locator/locator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ library;
import 'package:collection/collection.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:layrz_logging/layrz_logging.dart';
import 'package:layrz_models/src/api/api.dart';
import 'package:layrz_models/src/app/app.dart';
import 'package:layrz_models/src/assets/assets.dart';
import 'package:layrz_models/src/converters/converters.dart';
Expand All @@ -20,9 +21,4 @@ part 'src/locator.dart';
part 'src/mqtt_config.dart';
part 'src/locator_input.dart';

class LocatorApiResponse {
final Locator? locator;
final Map<String, dynamic> errors;

LocatorApiResponse({this.locator, this.errors = const {}});
}
36 changes: 20 additions & 16 deletions lib/src/locator/src/locator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -118,20 +118,21 @@ abstract class Locator with _$Locator {

final data = response.data;
if (data == null) {
onResponse?.call('INTERNAL_ERROR');
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/Locator/fetch(): No response from server");
return null;
}

final result = data['data']['locators'];
if (result == null) {
onResponse?.call('INTERNAL_ERROR');
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/Locator/fetch(): No result from server");
return null;
}

if (result['status'] != 'OK') {
onResponse?.call(result['status']);
final status = ApiStatus.fromJson(result['status']);
if (status != ApiStatus.ok) {
onResponse?.call(status.toJson());
return null;
}
if (result['result'] == null || (result['result'] as List).isEmpty) {
Expand Down Expand Up @@ -165,20 +166,21 @@ abstract class Locator with _$Locator {

final data = response.data;
if (data == null) {
onResponse?.call('INTERNAL_ERROR');
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/Locator/fetchAll(): No response from server");
return [];
}

final result = data['data']['locators'];
if (result == null) {
onResponse?.call('INTERNAL_ERROR');
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/Locator/fetchAll(): No result from server");
return [];
}

if (result['status'] != 'OK') {
onResponse?.call(result['status']);
final status = ApiStatus.fromJson(result['status']);
if (status != ApiStatus.ok) {
onResponse?.call(status.toJson());
return [];
}

Expand Down Expand Up @@ -218,20 +220,21 @@ abstract class Locator with _$Locator {

final data = response.data;
if (data == null) {
onResponse?.call('INTERNAL_ERROR');
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/Locator/expire(): No response from server");
return false;
}

final result = data['data']['expireLocators'];
if (result == null) {
onResponse?.call('INTERNAL_ERROR');
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/Locator/expire(): No result from server");
return false;
}

if (result['status'] != 'OK') {
onResponse?.call(result['status']);
final status = ApiStatus.fromJson(result['status']);
if (status != ApiStatus.ok) {
onResponse?.call(status.toJson());
return false;
}

Expand Down Expand Up @@ -267,20 +270,21 @@ abstract class Locator with _$Locator {

final data = response.data;
if (data == null) {
onResponse?.call('INTERNAL_ERROR');
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/Locator/expireMultiple(): No response from server");
return false;
}

final result = data['data']['expireLocators'];
if (result == null) {
onResponse?.call('INTERNAL_ERROR');
onResponse?.call(ApiStatus.internalError.toJson());
Log.error("layrz_models/Locator/expireMultiple(): No result from server");
return false;
}

if (result['status'] != 'OK') {
onResponse?.call(result['status']);
final status = ApiStatus.fromJson(result['status']);
if (status != ApiStatus.ok) {
onResponse?.call(status.toJson());
return false;
}

Expand Down
Loading
Loading