nico-tools 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +9 -13
- package/bin/commands/create-project.js +106 -0
- package/bin/commands/translate.js +12 -0
- package/bin/config/templates.js +9 -0
- package/bin/utils/file-utils.js +84 -0
- package/bin/utils/project-utils.js +49 -0
- package/package.json +4 -2
- package/templates/react-native/expo-clean-architecture/README.md +154 -0
- package/templates/react-native/expo-clean-architecture/app.config.ts +61 -0
- package/templates/react-native/expo-clean-architecture/assets/config/.gitkeep +3 -0
- package/templates/react-native/expo-clean-architecture/assets/fonts/SpaceMono-Regular.ttf +0 -0
- package/templates/react-native/expo-clean-architecture/assets/images/adaptive-icon.png +0 -0
- package/templates/react-native/expo-clean-architecture/assets/images/favicon.png +0 -0
- package/templates/react-native/expo-clean-architecture/assets/images/icon.png +0 -0
- package/templates/react-native/expo-clean-architecture/assets/images/partial-react-logo.png +0 -0
- package/templates/react-native/expo-clean-architecture/assets/images/react-logo.png +0 -0
- package/templates/react-native/expo-clean-architecture/assets/images/react-logo@2x.png +0 -0
- package/templates/react-native/expo-clean-architecture/assets/images/react-logo@3x.png +0 -0
- package/templates/react-native/expo-clean-architecture/assets/images/splash-icon.png +0 -0
- package/templates/react-native/expo-clean-architecture/babel.config.js +11 -0
- package/templates/react-native/expo-clean-architecture/docs/00-introduction.md +3 -0
- package/templates/react-native/expo-clean-architecture/docs/01-architecture.md +107 -0
- package/templates/react-native/expo-clean-architecture/package.json +78 -0
- package/templates/react-native/expo-clean-architecture/scripts/clean-src.sh +48 -0
- package/templates/react-native/expo-clean-architecture/scripts/generate-feature.sh +40 -0
- package/templates/react-native/expo-clean-architecture/src/app/(protected)/(tabs)/_layout.tsx +42 -0
- package/templates/react-native/expo-clean-architecture/src/app/(protected)/(tabs)/favorites.tsx +72 -0
- package/templates/react-native/expo-clean-architecture/src/app/(protected)/(tabs)/home.tsx +122 -0
- package/templates/react-native/expo-clean-architecture/src/app/(protected)/(tabs)/settings/_layout.tsx +5 -0
- package/templates/react-native/expo-clean-architecture/src/app/(protected)/(tabs)/settings/index.tsx +29 -0
- package/templates/react-native/expo-clean-architecture/src/app/(protected)/(tabs)/settings/profile.tsx +22 -0
- package/templates/react-native/expo-clean-architecture/src/app/(protected)/_layout.tsx +20 -0
- package/templates/react-native/expo-clean-architecture/src/app/(protected)/details.tsx +124 -0
- package/templates/react-native/expo-clean-architecture/src/app/(public)/_layout.tsx +18 -0
- package/templates/react-native/expo-clean-architecture/src/app/(public)/login.tsx +31 -0
- package/templates/react-native/expo-clean-architecture/src/app/_layout.tsx +33 -0
- package/templates/react-native/expo-clean-architecture/src/app/index.tsx +8 -0
- package/templates/react-native/expo-clean-architecture/src/core/constants/api-constants.ts +10 -0
- package/templates/react-native/expo-clean-architecture/src/core/constants/image-constants.ts +3 -0
- package/templates/react-native/expo-clean-architecture/src/core/constants/query-keys.ts +6 -0
- package/templates/react-native/expo-clean-architecture/src/core/constants/storage-keys.ts +3 -0
- package/templates/react-native/expo-clean-architecture/src/core/design/@types/color-scheme-state.ts +35 -0
- package/templates/react-native/expo-clean-architecture/src/core/design/@types/color-scheme.ts +12 -0
- package/templates/react-native/expo-clean-architecture/src/core/design/components/app-icon.tsx +16 -0
- package/templates/react-native/expo-clean-architecture/src/core/design/components/app-separator.tsx +26 -0
- package/templates/react-native/expo-clean-architecture/src/core/design/hooks/use-app-color-scheme.ts +52 -0
- package/templates/react-native/expo-clean-architecture/src/core/design/hooks/use-app-fonts.ts +12 -0
- package/templates/react-native/expo-clean-architecture/src/core/design/hooks/use-app-styles.ts +28 -0
- package/templates/react-native/expo-clean-architecture/src/core/design/theme/app-colors.ts +21 -0
- package/templates/react-native/expo-clean-architecture/src/core/design/theme/app-fonts.ts +16 -0
- package/templates/react-native/expo-clean-architecture/src/core/design/theme/app-sizes.ts +14 -0
- package/templates/react-native/expo-clean-architecture/src/core/di/injection-container.ts +53 -0
- package/templates/react-native/expo-clean-architecture/src/core/errors/index.ts +1 -0
- package/templates/react-native/expo-clean-architecture/src/core/helpers/@types.ts +23 -0
- package/templates/react-native/expo-clean-architecture/src/core/helpers/rest-client.ts +144 -0
- package/templates/react-native/expo-clean-architecture/src/core/helpers/result.ts +37 -0
- package/templates/react-native/expo-clean-architecture/src/core/helpers/usecase.ts +5 -0
- package/templates/react-native/expo-clean-architecture/src/core/hooks/use-network.ts +18 -0
- package/templates/react-native/expo-clean-architecture/src/core/i18n/@types/i18next.d.ts +11 -0
- package/templates/react-native/expo-clean-architecture/src/core/i18n/index.ts +19 -0
- package/templates/react-native/expo-clean-architecture/src/core/i18n/translations/fr.json +12 -0
- package/templates/react-native/expo-clean-architecture/src/core/services/local-storage-service-impl.ts +29 -0
- package/templates/react-native/expo-clean-architecture/src/core/services/local-storage-service.ts +26 -0
- package/templates/react-native/expo-clean-architecture/src/core/services/monitoring-service-impl.ts +15 -0
- package/templates/react-native/expo-clean-architecture/src/core/services/monitoring-service.ts +13 -0
- package/templates/react-native/expo-clean-architecture/src/core/services/network-service-impl.ts +40 -0
- package/templates/react-native/expo-clean-architecture/src/core/services/network-service.ts +16 -0
- package/templates/react-native/expo-clean-architecture/src/features/auth/@types/session-state.ts +38 -0
- package/templates/react-native/expo-clean-architecture/src/features/auth/@types/session-status-enum.ts +16 -0
- package/templates/react-native/expo-clean-architecture/src/features/auth/presentation/hooks/use-session.ts +18 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/data/datasources/favorites-datasource-impl.ts +25 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/data/datasources/favorites-datasource.ts +5 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/data/repositories/favorites-repository-impl.ts +46 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/domain/repositories/favorites-repository.ts +8 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/domain/usecases/add-to-favorites-usecase.ts +23 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/domain/usecases/clear-favorites-usecase.ts +23 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/domain/usecases/get-favorites-usecase.ts +24 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/domain/usecases/remove-from-favorites-usecase.ts +23 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/presentation/components/favorites-card.tsx +77 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/presentation/hooks/use-add-favorite.ts +24 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/presentation/hooks/use-clear-favorites.ts +22 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/presentation/hooks/use-favorites.ts +22 -0
- package/templates/react-native/expo-clean-architecture/src/features/favorites/presentation/hooks/use-remove-favorite.ts +24 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/data/datasources/movies-datasource-impl.ts +50 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/data/datasources/movies-datasource.ts +8 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/data/models/movie-details-model.ts +67 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/data/models/movie-model.ts +30 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/data/models/tmdb-response-model.ts +6 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/data/repositories/movies-repository-impl.ts +34 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/domain/entities/movie-details-entity.ts +28 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/domain/entities/movie-entity.ts +6 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/domain/repositories/movies-repository.ts +25 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/domain/usecases/get-movie-details-usecase.ts +26 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/domain/usecases/get-random-movies-usecase.ts +24 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/domain/usecases/search-movies-usecase.ts +23 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/presentation/components/movie-tile.tsx +69 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/presentation/hooks/use-movie-details.ts +22 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/presentation/hooks/use-random-movies.ts +22 -0
- package/templates/react-native/expo-clean-architecture/src/features/movies/presentation/hooks/use-search-movies.ts +22 -0
- package/templates/react-native/expo-clean-architecture/tests/core/services/local-storage-service-impl.test.ts +108 -0
- package/templates/react-native/expo-clean-architecture/tests/core/services/monitoring-service.test.ts +74 -0
- package/templates/react-native/expo-clean-architecture/tests/core/services/network-service.test.ts +117 -0
- package/templates/react-native/expo-clean-architecture/tests/features/auth/presentation/hooks/use-session.test.ts +69 -0
- package/templates/react-native/expo-clean-architecture/tests/features/favorites/data/datasources/favorites-datasource.test.ts +69 -0
- package/templates/react-native/expo-clean-architecture/tests/features/favorites/data/repositories/favorites-repository-impl.test.ts +124 -0
- package/templates/react-native/expo-clean-architecture/tests/features/favorites/domain/usecases/add-to-favorites-usecase.test.ts +54 -0
- package/templates/react-native/expo-clean-architecture/tests/features/favorites/domain/usecases/clear-favorites-usecase.test.ts +44 -0
- package/templates/react-native/expo-clean-architecture/tests/features/favorites/domain/usecases/get-favorites-usecase.test.ts +74 -0
- package/templates/react-native/expo-clean-architecture/tests/features/favorites/domain/usecases/remove-from-favorites-usecase.test.ts +52 -0
- package/templates/react-native/expo-clean-architecture/tests/setup.ts +9 -0
- package/templates/react-native/expo-clean-architecture/tsconfig.json +20 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RestClientOptions,
|
|
3
|
+
GetRequestConfig,
|
|
4
|
+
PostRequestConfig,
|
|
5
|
+
PutRequestConfig,
|
|
6
|
+
DeleteRequestConfig,
|
|
7
|
+
RequestConfig,
|
|
8
|
+
} from "@/core/helpers/@types";
|
|
9
|
+
|
|
10
|
+
export abstract class RestClient {
|
|
11
|
+
private readonly baseUrl: string;
|
|
12
|
+
private readonly defaultHeaders: Record<string, string>;
|
|
13
|
+
|
|
14
|
+
constructor(options: RestClientOptions) {
|
|
15
|
+
this.baseUrl = options.baseUrl || "";
|
|
16
|
+
this.defaultHeaders = options.headers || {};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Perform a GET request to the server
|
|
21
|
+
* @param options - The options for the request
|
|
22
|
+
* @returns The resource
|
|
23
|
+
*/
|
|
24
|
+
async get<T>(options: GetRequestConfig): Promise<T> {
|
|
25
|
+
try {
|
|
26
|
+
const response = await fetch(
|
|
27
|
+
this.getFullUrl(options.path, options.queryParameters),
|
|
28
|
+
{
|
|
29
|
+
...options,
|
|
30
|
+
headers: this.getHeaders(options),
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
return this.handleResponse<T>(response);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
return this.handleError(error as Error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Perform a POST request to the server
|
|
42
|
+
* @param config - The config for the request
|
|
43
|
+
* @returns The resource
|
|
44
|
+
*/
|
|
45
|
+
async post<T>(config: PostRequestConfig): Promise<T> {
|
|
46
|
+
try {
|
|
47
|
+
const response = await fetch(this.getFullUrl(config.path), {
|
|
48
|
+
...config,
|
|
49
|
+
method: "POST",
|
|
50
|
+
body: JSON.stringify(config.body),
|
|
51
|
+
headers: this.getHeaders(config),
|
|
52
|
+
});
|
|
53
|
+
return this.handleResponse<T>(response);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return this.handleError(error as Error);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Perform a PUT request to the server
|
|
61
|
+
* @param config - The config for the request
|
|
62
|
+
* @returns The resource
|
|
63
|
+
*/
|
|
64
|
+
async put<T>(config: PutRequestConfig): Promise<T> {
|
|
65
|
+
try {
|
|
66
|
+
const response = await fetch(this.getFullUrl(config.path), {
|
|
67
|
+
...config,
|
|
68
|
+
method: "PUT",
|
|
69
|
+
body: JSON.stringify(config.body),
|
|
70
|
+
headers: this.getHeaders(config),
|
|
71
|
+
});
|
|
72
|
+
return this.handleResponse<T>(response);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
return this.handleError(error as Error);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Perform a DELETE request to the server
|
|
80
|
+
* @param config - The config for the request
|
|
81
|
+
* @returns The resource
|
|
82
|
+
*/
|
|
83
|
+
async delete<T>(config: DeleteRequestConfig): Promise<T> {
|
|
84
|
+
try {
|
|
85
|
+
const response = await fetch(this.getFullUrl(config.path), {
|
|
86
|
+
...config,
|
|
87
|
+
method: "DELETE",
|
|
88
|
+
headers: this.getHeaders(config),
|
|
89
|
+
});
|
|
90
|
+
return this.handleResponse<T>(response);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
return this.handleError(error as Error);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get the full URL for a request
|
|
98
|
+
* @param path - The path of the resource
|
|
99
|
+
* @param queryParameters - The query parameters for the request - _Optional_
|
|
100
|
+
* @returns The full URL
|
|
101
|
+
*/
|
|
102
|
+
private getFullUrl(
|
|
103
|
+
path: string,
|
|
104
|
+
queryParameters?: Record<string, string>
|
|
105
|
+
): string {
|
|
106
|
+
const queryString = queryParameters
|
|
107
|
+
? `?${new URLSearchParams(queryParameters).toString()}`
|
|
108
|
+
: "";
|
|
109
|
+
return `${this.baseUrl}${path}${queryString}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Concatenate the default headers with the request headers
|
|
114
|
+
* @param config - The config for the request
|
|
115
|
+
* @returns The headers
|
|
116
|
+
*/
|
|
117
|
+
private getHeaders(config?: RequestConfig): Record<string, string> {
|
|
118
|
+
return {
|
|
119
|
+
...this.defaultHeaders,
|
|
120
|
+
...config?.options?.headers?.toString,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Handle the response from the server
|
|
126
|
+
* @param response - The response from the server
|
|
127
|
+
* @returns The resource as a JSON object, or throws an error if the response is not ok
|
|
128
|
+
*/
|
|
129
|
+
private handleResponse<T>(response: Response): Promise<T> {
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
throw new Error(response.statusText);
|
|
132
|
+
}
|
|
133
|
+
return response.json();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Handle the error from the server
|
|
138
|
+
* @param error - The error from the server
|
|
139
|
+
* @returns The error
|
|
140
|
+
*/
|
|
141
|
+
private handleError(error: Error): Promise<never> {
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export abstract class Result<T> {
|
|
2
|
+
data: T | null;
|
|
3
|
+
error: Error | null;
|
|
4
|
+
|
|
5
|
+
constructor(data: T, error: Error | null) {
|
|
6
|
+
this.data = data;
|
|
7
|
+
this.error = error;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
get isSuccess(): boolean {
|
|
11
|
+
return this instanceof Success;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get isFailure(): boolean {
|
|
15
|
+
return this instanceof Failure;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static success<T>(data: T): Success<T> {
|
|
19
|
+
return new Success(data);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static failure(error: any): Failure {
|
|
23
|
+
return new Failure(error);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class Success<T> extends Result<T> {
|
|
28
|
+
constructor(data: T) {
|
|
29
|
+
super(data, null);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class Failure extends Result<null> {
|
|
34
|
+
constructor(error: Error) {
|
|
35
|
+
super(null, error);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import container from "@/core/di/injection-container";
|
|
3
|
+
import { NetworkService } from "@/core/services/network-service";
|
|
4
|
+
|
|
5
|
+
export function useNetwork(): { isOnline: boolean } {
|
|
6
|
+
const networkService = container.get<NetworkService>(NetworkService);
|
|
7
|
+
const [isOnline, setIsOnline] = useState<boolean>(false);
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const unsubscribe = networkService.subscribe(setIsOnline);
|
|
11
|
+
|
|
12
|
+
return () => {
|
|
13
|
+
unsubscribe();
|
|
14
|
+
};
|
|
15
|
+
}, [networkService]);
|
|
16
|
+
|
|
17
|
+
return { isOnline };
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import i18n from "i18next";
|
|
2
|
+
import { initReactI18next } from "react-i18next";
|
|
3
|
+
import fr from "@/core/i18n/translations/fr.json";
|
|
4
|
+
|
|
5
|
+
const resources = {
|
|
6
|
+
fr: {
|
|
7
|
+
translation: fr,
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
i18n.use(initReactI18next).init({
|
|
12
|
+
resources,
|
|
13
|
+
lng: "fr",
|
|
14
|
+
react: {
|
|
15
|
+
useSuspense: true,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export default i18n;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { injectable } from "inversify";
|
|
2
|
+
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
3
|
+
|
|
4
|
+
import { LocalStorageService } from "@/core/services/local-storage-service";
|
|
5
|
+
|
|
6
|
+
@injectable()
|
|
7
|
+
export class LocalStorageServiceImpl extends LocalStorageService {
|
|
8
|
+
async getItem(key: string, defaultValue: string): Promise<string> {
|
|
9
|
+
const value = await AsyncStorage.getItem(key);
|
|
10
|
+
|
|
11
|
+
if (value === null) {
|
|
12
|
+
return defaultValue;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async setItem(key: string, value: string): Promise<void> {
|
|
19
|
+
await AsyncStorage.setItem(key, value);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async removeItem(key: string): Promise<void> {
|
|
23
|
+
await AsyncStorage.removeItem(key);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async clear(): Promise<void> {
|
|
27
|
+
await AsyncStorage.clear();
|
|
28
|
+
}
|
|
29
|
+
}
|
package/templates/react-native/expo-clean-architecture/src/core/services/local-storage-service.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export abstract class LocalStorageService {
|
|
2
|
+
/**
|
|
3
|
+
* Get an item from the local storage
|
|
4
|
+
* @param key The key of the item to get
|
|
5
|
+
* @returns The item or null if it doesn't exist
|
|
6
|
+
*/
|
|
7
|
+
abstract getItem(key: string, defaultValue: string): Promise<string>;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Set an item in the local storage
|
|
11
|
+
* @param key The key of the item to set
|
|
12
|
+
* @param value The value of the item to set
|
|
13
|
+
*/
|
|
14
|
+
abstract setItem(key: string, value: string): Promise<void>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Remove an item from the local storage
|
|
18
|
+
* @param key The key of the item to remove
|
|
19
|
+
*/
|
|
20
|
+
abstract removeItem(key: string): Promise<void>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Clear the local storage
|
|
24
|
+
*/
|
|
25
|
+
abstract clear(): Promise<void>;
|
|
26
|
+
}
|
package/templates/react-native/expo-clean-architecture/src/core/services/monitoring-service-impl.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { injectable } from "inversify";
|
|
2
|
+
import {
|
|
3
|
+
recordError,
|
|
4
|
+
getCrashlytics,
|
|
5
|
+
} from "@react-native-firebase/crashlytics";
|
|
6
|
+
|
|
7
|
+
import { MonitoringService } from "@/core/services/monitoring-service";
|
|
8
|
+
|
|
9
|
+
@injectable()
|
|
10
|
+
export class MonitoringServiceImpl implements MonitoringService {
|
|
11
|
+
report(error: Error): void {
|
|
12
|
+
const crashlytics = getCrashlytics();
|
|
13
|
+
recordError(crashlytics, error);
|
|
14
|
+
}
|
|
15
|
+
}
|
package/templates/react-native/expo-clean-architecture/src/core/services/monitoring-service.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { injectable } from "inversify";
|
|
2
|
+
import {
|
|
3
|
+
recordError,
|
|
4
|
+
getCrashlytics,
|
|
5
|
+
} from "@react-native-firebase/crashlytics";
|
|
6
|
+
|
|
7
|
+
export abstract class MonitoringService {
|
|
8
|
+
/**
|
|
9
|
+
* Report an error to the monitoring service
|
|
10
|
+
* @param error - The error to report
|
|
11
|
+
*/
|
|
12
|
+
abstract report(error: Error): void;
|
|
13
|
+
}
|
package/templates/react-native/expo-clean-architecture/src/core/services/network-service-impl.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { injectable } from "inversify";
|
|
2
|
+
import {
|
|
3
|
+
getNetworkStateAsync,
|
|
4
|
+
addNetworkStateListener,
|
|
5
|
+
NetworkState,
|
|
6
|
+
} from "expo-network";
|
|
7
|
+
import { NetworkService } from "@/core/services/network-service";
|
|
8
|
+
import { NetworkStateChangeCallback } from "@/core/services/network-service";
|
|
9
|
+
|
|
10
|
+
@injectable()
|
|
11
|
+
export class NetworkServiceImpl implements NetworkService {
|
|
12
|
+
private listeners: Set<NetworkStateChangeCallback> = new Set();
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
// Initialize the listener when the service is created
|
|
16
|
+
addNetworkStateListener(this.handleNetworkStateChange);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private handleNetworkStateChange = (state: NetworkState) => {
|
|
20
|
+
const isOnline = state.isInternetReachable ?? false;
|
|
21
|
+
this.listeners.forEach((callback) => callback(isOnline));
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
async isOnline(): Promise<boolean> {
|
|
25
|
+
const networkState = await getNetworkStateAsync();
|
|
26
|
+
return networkState.isInternetReachable ?? false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
subscribe(callback: NetworkStateChangeCallback): () => void {
|
|
30
|
+
this.listeners.add(callback);
|
|
31
|
+
|
|
32
|
+
// Initial state check
|
|
33
|
+
this.isOnline().then((isOnline) => callback(isOnline));
|
|
34
|
+
|
|
35
|
+
// Return unsubscribe function
|
|
36
|
+
return () => {
|
|
37
|
+
this.listeners.delete(callback);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type NetworkStateChangeCallback = (isOnline: boolean) => void;
|
|
2
|
+
|
|
3
|
+
export abstract class NetworkService {
|
|
4
|
+
/**
|
|
5
|
+
* Check if the device has an internet connection
|
|
6
|
+
* @returns true if the device has an internet connection, false otherwise
|
|
7
|
+
*/
|
|
8
|
+
abstract isOnline(): Promise<boolean>;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Subscribe to the network state change
|
|
12
|
+
* @param callback - The function to be called when the network state changes
|
|
13
|
+
* @returns A function to unsubscribe from the network state change
|
|
14
|
+
*/
|
|
15
|
+
abstract subscribe(callback: NetworkStateChangeCallback): () => void;
|
|
16
|
+
}
|
package/templates/react-native/expo-clean-architecture/src/features/auth/@types/session-state.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { SessionStatus } from "@/features/auth/@types/session-status-enum";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents the state of a user session and associated actions.
|
|
5
|
+
*/
|
|
6
|
+
export interface SessionState {
|
|
7
|
+
/**
|
|
8
|
+
* The current status of the session.
|
|
9
|
+
*/
|
|
10
|
+
status: SessionStatus;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Logs in the user with the provided parameters.
|
|
14
|
+
* @param {LoginParamsEntity} params - The parameters required to log in the user.
|
|
15
|
+
*/
|
|
16
|
+
login: () => Promise<void>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Logs out the current user.
|
|
20
|
+
*/
|
|
21
|
+
logout: () => void;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Attempts to silently log in the user without their intervention.
|
|
25
|
+
*/
|
|
26
|
+
silentLogin: () => Promise<void>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Checks if the user is currently authenticated.
|
|
30
|
+
* @returns {boolean} `true` if the user is authenticated, otherwise `false`.
|
|
31
|
+
*/
|
|
32
|
+
isAuthenticated: () => boolean;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Indicates whether the session state has been initialized.
|
|
36
|
+
*/
|
|
37
|
+
isInitialized: boolean;
|
|
38
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export enum SessionStatus {
|
|
2
|
+
/**
|
|
3
|
+
* The user is currently being authenticated or logged out.
|
|
4
|
+
*/
|
|
5
|
+
LOADING,
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The user is authenticated.
|
|
9
|
+
*/
|
|
10
|
+
AUTHENTICATED,
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The user is not authenticated.
|
|
14
|
+
*/
|
|
15
|
+
UNAUTHENTICATED,
|
|
16
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
import { SessionStatus } from "@/features/auth/@types/session-status-enum";
|
|
3
|
+
import { SessionState } from "@/features/auth/@types/session-state";
|
|
4
|
+
|
|
5
|
+
export const useSession = create<SessionState>((set, get) => ({
|
|
6
|
+
status: SessionStatus.UNAUTHENTICATED,
|
|
7
|
+
login: async () => {
|
|
8
|
+
set({ status: SessionStatus.AUTHENTICATED });
|
|
9
|
+
},
|
|
10
|
+
logout: async () => {
|
|
11
|
+
set({ status: SessionStatus.UNAUTHENTICATED });
|
|
12
|
+
},
|
|
13
|
+
silentLogin: async () => {
|
|
14
|
+
set({ status: SessionStatus.AUTHENTICATED });
|
|
15
|
+
},
|
|
16
|
+
isAuthenticated: () => get().status === SessionStatus.AUTHENTICATED,
|
|
17
|
+
isInitialized: false,
|
|
18
|
+
}));
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { injectable, inject } from "inversify";
|
|
2
|
+
|
|
3
|
+
import { LocalStorageService } from "@/core/services/local-storage-service";
|
|
4
|
+
import { STORAGE_KEYS } from "@/core/constants/storage-keys";
|
|
5
|
+
import { FavoritesDatasource } from "@/features/favorites/data/datasources/favorites-datasource";
|
|
6
|
+
|
|
7
|
+
@injectable()
|
|
8
|
+
export class FavoritesDatasourceImpl implements FavoritesDatasource {
|
|
9
|
+
constructor(
|
|
10
|
+
@inject(LocalStorageService)
|
|
11
|
+
private readonly localStorageService: LocalStorageService
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
async getFavorites(): Promise<string> {
|
|
15
|
+
return await this.localStorageService.getItem(STORAGE_KEYS.FAVORITES, "[]");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async setFavorites(favorites: string): Promise<void> {
|
|
19
|
+
await this.localStorageService.setItem(STORAGE_KEYS.FAVORITES, favorites);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async clearFavorites(): Promise<void> {
|
|
23
|
+
await this.localStorageService.removeItem(STORAGE_KEYS.FAVORITES);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { inject, injectable } from "inversify";
|
|
2
|
+
import { FavoritesRepository } from "@/features/favorites/domain/repositories/favorites-repository";
|
|
3
|
+
import { MovieEntity } from "@/features/movies/domain/entities/movie-entity";
|
|
4
|
+
import { FavoritesDatasource } from "@/features/favorites/data/datasources/favorites-datasource";
|
|
5
|
+
|
|
6
|
+
@injectable()
|
|
7
|
+
export class FavoritesRepositoryImpl implements FavoritesRepository {
|
|
8
|
+
constructor(
|
|
9
|
+
@inject(FavoritesDatasource)
|
|
10
|
+
private readonly favoritesDatasource: FavoritesDatasource
|
|
11
|
+
) {}
|
|
12
|
+
|
|
13
|
+
async addFavorite(movie: MovieEntity): Promise<void> {
|
|
14
|
+
const currentFavorites = await this.favoritesDatasource.getFavorites();
|
|
15
|
+
|
|
16
|
+
const newFavorites = [...JSON.parse(currentFavorites), movie];
|
|
17
|
+
|
|
18
|
+
await this.favoritesDatasource.setFavorites(JSON.stringify(newFavorites));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async removeFavorite(movieId: string): Promise<void> {
|
|
22
|
+
const currentFavorites = await this.favoritesDatasource.getFavorites();
|
|
23
|
+
|
|
24
|
+
const favorites = JSON.parse(currentFavorites);
|
|
25
|
+
|
|
26
|
+
const newFavorites = favorites.filter((movie: MovieEntity) => {
|
|
27
|
+
const parsedMovie = { ...movie };
|
|
28
|
+
|
|
29
|
+
return parsedMovie.id !== movieId;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
await this.favoritesDatasource.setFavorites(JSON.stringify(newFavorites));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async getFavorites(): Promise<MovieEntity[]> {
|
|
36
|
+
const currentFavorites = await this.favoritesDatasource.getFavorites();
|
|
37
|
+
|
|
38
|
+
const favorites = JSON.parse(currentFavorites);
|
|
39
|
+
|
|
40
|
+
return favorites;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async clearFavorites(): Promise<void> {
|
|
44
|
+
await this.favoritesDatasource.clearFavorites();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MovieEntity } from "@/features/movies/domain/entities/movie-entity";
|
|
2
|
+
|
|
3
|
+
export abstract class FavoritesRepository {
|
|
4
|
+
abstract getFavorites(): Promise<MovieEntity[]>;
|
|
5
|
+
abstract addFavorite(movie: MovieEntity): Promise<void>;
|
|
6
|
+
abstract removeFavorite(movieId: string): Promise<void>;
|
|
7
|
+
abstract clearFavorites(): Promise<void>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { inject, injectable } from "inversify";
|
|
2
|
+
|
|
3
|
+
import { Usecase } from "@/core/helpers/usecase";
|
|
4
|
+
import { Result } from "@/core/helpers/result";
|
|
5
|
+
import { FavoritesRepository } from "@/features/favorites/domain/repositories/favorites-repository";
|
|
6
|
+
import { MovieEntity } from "@/features/movies/domain/entities/movie-entity";
|
|
7
|
+
@injectable()
|
|
8
|
+
export class AddToFavoritesUsecase implements Usecase<MovieEntity, void> {
|
|
9
|
+
constructor(
|
|
10
|
+
@inject(FavoritesRepository)
|
|
11
|
+
private readonly favoritesRepository: FavoritesRepository
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
async execute(movie: MovieEntity): Promise<Result<void>> {
|
|
15
|
+
try {
|
|
16
|
+
await this.favoritesRepository.addFavorite(movie);
|
|
17
|
+
|
|
18
|
+
return Result.success(null);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
return Result.failure(error);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { inject, injectable } from "inversify";
|
|
2
|
+
|
|
3
|
+
import { Usecase } from "@/core/helpers/usecase";
|
|
4
|
+
import { Result } from "@/core/helpers/result";
|
|
5
|
+
import { FavoritesRepository } from "@/features/favorites/domain/repositories/favorites-repository";
|
|
6
|
+
|
|
7
|
+
@injectable()
|
|
8
|
+
export class ClearFavoritesUsecase implements Usecase<void, void> {
|
|
9
|
+
constructor(
|
|
10
|
+
@inject(FavoritesRepository)
|
|
11
|
+
private readonly favoritesRepository: FavoritesRepository
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
async execute(): Promise<Result<void>> {
|
|
15
|
+
try {
|
|
16
|
+
await this.favoritesRepository.clearFavorites();
|
|
17
|
+
|
|
18
|
+
return Result.success(null);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
return Result.failure(error);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { inject, injectable } from "inversify";
|
|
2
|
+
|
|
3
|
+
import { Result } from "@/core/helpers/result";
|
|
4
|
+
import { Usecase } from "@/core/helpers/usecase";
|
|
5
|
+
import { FavoritesRepository } from "@/features/favorites/domain/repositories/favorites-repository";
|
|
6
|
+
import { MovieEntity } from "@/features/movies/domain/entities/movie-entity";
|
|
7
|
+
|
|
8
|
+
@injectable()
|
|
9
|
+
export class GetFavoritesUsecase implements Usecase<void, MovieEntity[]> {
|
|
10
|
+
constructor(
|
|
11
|
+
@inject(FavoritesRepository)
|
|
12
|
+
private readonly favoritesRepository: FavoritesRepository
|
|
13
|
+
) {}
|
|
14
|
+
|
|
15
|
+
async execute(): Promise<Result<MovieEntity[]>> {
|
|
16
|
+
try {
|
|
17
|
+
const favorites = await this.favoritesRepository.getFavorites();
|
|
18
|
+
|
|
19
|
+
return Result.success(favorites);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
return Result.failure(error);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { inject, injectable } from "inversify";
|
|
2
|
+
|
|
3
|
+
import { Usecase } from "@/core/helpers/usecase";
|
|
4
|
+
import { Result } from "@/core/helpers/result";
|
|
5
|
+
import { FavoritesRepository } from "@/features/favorites/domain/repositories/favorites-repository";
|
|
6
|
+
|
|
7
|
+
@injectable()
|
|
8
|
+
export class RemoveFromFavoritesUsecase implements Usecase<string, void> {
|
|
9
|
+
constructor(
|
|
10
|
+
@inject(FavoritesRepository)
|
|
11
|
+
private readonly favoritesRepository: FavoritesRepository
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
async execute(movieId: string): Promise<Result<void>> {
|
|
15
|
+
try {
|
|
16
|
+
await this.favoritesRepository.removeFavorite(movieId);
|
|
17
|
+
|
|
18
|
+
return Result.success(null);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
return Result.failure(error);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|