davidcr01 / WordlePlus

Repository to store all the documentation, files and structure of my Final Degree Project (TFG in Spanish). The main goal is to develop, as full-stack web developer, a remodel of the Wordle game, including more features and functionalities using Ionic, Django REST Api and PostgreSQL.
1 stars 0 forks source link

Frontend should use an API service (S3) #19

Closed davidcr01 closed 1 year ago

davidcr01 commented 1 year ago

Description

In the frontend, instead of calling the API URLs directly, it would be great to group all the API calls into a service, and use that service.

davidcr01 commented 1 year ago

Description

Development

The API service has been created in the services folder. It looks like:

export class ApiService {

    private readonly baseURL = 'http://localhost:8080'
    constructor(private http: HttpClient) { }

    login(credentials: any): Observable<any> {
        const url = `${this.baseURL}/api-token-auth/`;
        return this.http.post(url, credentials);
    }

    createPlayer(userData: any): Observable<any> {
        const url = `${this.baseURL}/api/players/`;
        const body = { user: userData };
        return this.http.post(url, body);
    }

    createUser(userData: any): Observable<any> {
        let url = `${this.baseURL}/api/users/`;
        return this.http.post(url, userData);
    }

The baseURL is defined, and also the methods supported by the backend. In this way, if an URL changes, it is only necessary to change it in this file and not in every place an URL is used.

This is a good practice of clean code and integrity. All tests have been passed creating and logging users.

Now, for example, to login the user we use the following code snippet:

login() {
    const credentials = {
      username: this.loginForm.get('username').value,
      password: this.loginForm.get('password').value,
    }

    this.apiService.login(credentials).subscribe(
      async (response) => {
        // Store the token in the local storage
        this.errorMessage = ''
        const encryptedToken = this.encryptionService.encryptData(response.token);
        await this.storageService.setAccessToken(encryptedToken);

        this.router.navigateByUrl('');
      },
      (error) => {
        console.error('Log in error', error);
        this.errorMessage = 'Provided credentials are not correct'
      }
    );

Notice the this.apiService.login() method is used. Notice that it is necessary to import this service in the necessary pages, and also declare it as a provider in the name.module.ts file.

davidcr01 commented 1 year ago

Update Report

Fix expiration token

A little fix has been added to check if the token is expired also when using the api-token-auth path. Before, a token could be expired and it won't be removed until an API call was done with the token. So, it could be possible that a user with an expired token tried to log into the application, and could be redirected again to the login page due to the expired token.

By this way, the expiration is also checked when using the URL to obtain tokens. This has been performed by implementing the token view:

class CustomObtainAuthToken(ObtainAuthToken):
    def post(self, request, *args, **kwargs):
        serializer = self.serializer_class(data=request.data)
        serializer.is_valid(raise_exception=True)

        user = serializer.validated_data['user']
        token, created = Token.objects.get_or_create(user=user)

        if not created and token.created < timezone.now() - timedelta(seconds=settings.TOKEN_EXPIRED_AFTER_SECONDS):
            # Token expired, generate a new one
            token.delete()
            token = Token.objects.create(user=user)

        # Serialize the token along with any other data you want to include in the response
        response_data = {
            'token': token.key
        }

        return Response(response_data, status=status.HTTP_200_OK)

Is similar to the middleware defined in #15. Now, the token expiration is checked when returning it and when using it.

Now, instead of using:

path('api-token-auth/', ObtainAuthToken.as_view())

we must use:

from djapi.views import CustomObtainAuthToken
....
path('api-token-auth/', CustomObtainAuthToken.as_view())