Today, I present you
the most successful tutorial on my channel, build a clone ready for deployment
in production of the streaming service the most famous music in the world, Spotify. To do this, on the front-end side, we
will use Angular in its latest version with Standalone Components,
Signal and the new Control Flow. Regarding the design, we will use
Bootstrap 5.3 with the aim of having a responsive design, but also to go
quickly for the implementation of our UI. On the back-end side, we will use Spring Boot and
PostgreSQL, two very robust technologies which have proven themselves and which will enable
to accommodate a very large number of users. We will also set up authentication
via Google and password, creating accounts and forgetting passwords via Auth0
in order to secure our application. Regarding the functionalities of
our application, our users will be able to add music, listen to it,
search for music and manage favorites. We will start by setting up
our front, that is to say the generation. The generation of our application
Angular and its configuration. To do this, we will type
ng-new spotify-clone-front. We will use the SCSS style and we will generate
all our components in stand-alone mode. So no, no pre-rendering or SSR. In the meantime, I'm preparing to
open the project with my IDE. So here my project is open in my idea
we're going to wait a little longer, that's it finished npm has successfully completed its installation, we continue
on the installation of bootstrap we are doing well be careful to position yourself in the file of
our project and then we use the Angular cli to install our bootstrap package where it is ng
add at sign ng drawn bootstrap slash ng bootstrap we validate here we answer the questions we answer
so yes then we will customize bootstrap for that it matches as much as possible with the style of Spotify
To do this we will create a folder in assets which will be called scss then we will create a
scss style sheet which will be called underscore bootstrap pulled variables content
of this stylesheet is available in the description you have a link that will help you
bring it directly, I copy paste it and content of this file what we notice and the
principle of this file is to overload the bootstrap variables variables that can
be for example linked in this case to the size of the h1 tags but there are lots of things like
example the color of the background the fact that we force dark mode font type etc etc one
once this file is created you will have to import it we go to the css points style file
which is the central style file and we will import our file just before the css of
bootstrap thanks to the keywords import we type in 7 scss and bootstrap variables then we move on to the
customization of our html index here we go force bootstrap to go into dark mode with the
data bs theme equals dark and we will also import a font to once again stick to the style
from spotify this font is the noto without us matter here now that's cool we're going to
render in the app component points html and on will validate that our bootstrap and our config
works well, we check and delete everything We add a dike with we are going to put
inside a button with button class and primary button and We are going to make a hello bootstrap. Then, we will be able to run our server. Either you have IntelliJ or WebStorm, like me. In this case, you have
just press play. Either you might be on VS Code or something. In these cases, you can open
a terminal and run ng serve. Then you just have to
go to localhost 4200. I'm going to use the IDE instead. Then we open our
browser on localhost 4200. And we notice that the background is dark. And we have the green of Spotify
with Spotify button style. We have validated that our bootstrap works
and that our configuration is valid. We start again with our IDE. We will stop the server like this. We open our terminal and we will add our
fontosome icon library thanks to Angular. We type ng add atrase for
awesome slash angular-fontosome. There, we answer yes. We choose fontosome 6 and we
takes the free icons. There it's done. We return to our IDE. We will create a shared folder and we create a TypeScript file
which we will call font. Thierry Awesome Thierry Icons. Thierry Awesome Thierry Icons. The purpose of this file and this class,
it will be to contain the icons that which we will use in our application. We don't want to import all the icons from this
library to avoid having an application too big and obviously load the icons for nothing. We type export const font awesome icons, which
is of type icon definition, it is an array. And it is in this table that we will define
the icons that we want to put or not. We're going to do a little test, we're going to import a
icon called fa-circle-play, like this. Then we go back, we're going to go
rather in the app-component class to initialize this library. We will need the fa-icon-library service
that we are going to inject, like this. We will also need to implement
onInit on the side of this component, root component, where we will call a
method that we will create in a moment. It's called font-init-font-awesome, which we
created in this way, and where we will come call the service that we just injected
before with the add-icon method, like this. And we're going to call the method, sorry, the variable,
that is to say the table that we created just before. Then, we will carry out a small test to
validate that this library works well. First, we will import
the font-awesome-module. In this component. See using icons in the template. We will then be in the template. We will add fa-icon with the attribute
icons, named CirclePlay, which is the icon that we imported just before. We run our server
if it is not done yet. And we go to the side of our browser and wait
and we see here that the icon is present ok perfect now we are going to design our layout we
we come back to our idea and we surrender on the app component html if it is not already there
case we do a check, erase everything and will start by adding a div with a class
css containers fluid padding top 3 and viewport 8 200 another div with a raw class and 8 main
raw that we will create in a little while more another div which will be dedicated to the column
which will be on the left therefore t no tsm no dmd block col md3 col xl2 navigation pe-2 and And h-100 in
this div will be there, we will create it right after there will be navigation and library like that
you can visualize then we will create a another div which will be the main column
with collar 12 collar sm 12 collar md 9 collar xl 10 panning start 0 and h8 100% then here we will place our
and hours and our main div with the following classes so make bottom of padding 3
background dark over flow scroll and full and hand happy and in the latter we will place the router
outlet ok we are good for our layout it gives us It remains to write the part and css first of all we will
import the variable file like these will add the class is main euro which contains
is with the variable main road is we also have the full class is main content with height
who made him happy is now we are going take care of the two components navigation and
bookstore which are in the left column as you can see on spotify here
left column we open our terminal and we will use the angular cli thanks to the command
nggc we are going to use an out or row correctly and we will call it navigation We open the HTML, we delete everything
what is inside. We open a div with a class roundedTo,
padding2, grid and background dark. In this div, we will put a tag
ul with a nav and flex colon class. Then, a li tag which will contain nav item. Then, an a tag which will contain a link with
a nav link class, flex align item center. The attribute, we are going to put an attribute
router link because this link, it will allow us to return home. It's going to be equal slash to root. And we will also ensure that
when we click and when we are on the root URL, home remains blank. For this, we put active like this. We're going to go to the line and we're going to
also add router active link. This is so that the class is active only
and only if we are on the root address. In this link there is an icon that we will
bring right after which is called home and a div that will just contain home. We're going to have a second menu entry. Here, with a nav item class and a link
in the same way as the one above. So a nav link. And flex align item center with a
router link which this time will be search. And the same with class when
she is active with active. An icon that will be called search. And a class with margin end 3 and a class
navicode which must also be found here we close this tag and we also add the div which
contains search inside finally we add the fontosome module and we add a
small active class here with color white which is contained in our scss file like this
now if we go back to our browser we need to go to the app component.html
and add our app navigation component we also have to import our two little ones
icons that are fa home and fa search with everything that normally we should actually have
home and search in this way regarding concerns bookstore we return to our
terminal and type nggc layout slash library on open this component we erase what is there
inside we add a div with a class so margin top 2 rounded top
2 padding y 2 padding start 2 padding end 3 dark background here we add more
a new div with flex justify Content, Between, AlignItemCenter and TextSecondary. Another small div with this time
Deflex and AlignItemCenter and MarginCenter3. In it there is a FontSumIcone with
a NavIcone class and a MarginEnd of 3. And our icon is called Book. Then there is a div that contains YourLibrary. We're just going to import right away
the icon, that way we remember it. There it's done. And in Bookstore, we also go
import the FontSumModule. Back in our HTML, we will add a link,
an A tag, which will contain a link which will subsequently allow you to add music. And this link is symbolized by a Plus icon,
which is a NavIcone class and is called Plus, which we will immediately import, FaPlus. So, let's see what
it gives on the browser side. We always have to add
this component with the import. That's it, so there we have
YourLibrary is correct. We have our layout. Now we will move on to the
generation of our backend. In the description, you will find a link which
will take you back to the start.spring.io site. This is the link that I will show you right away. And this web application allows
bootstrap your application directly, By having the possibility of
specify the dependencies you want, the language, Spring version, etc. So I've already done that for you. You just have to click. You can see a little bit of the
dependencies that I added. So, nothing crazy there. There is Web Security for API security, Load
Client to manage authentication, Liquibase to manage database schemas,
Postgre, the driver because we're going to need it, and Spring Data JPA for the persistence layer. So, we click on Generate. Next, we will open a File Explorer. We take the zip and copy it
in our favorite folder. Once we've done that,
we open it in our IDE. Once the project is open in your IDE,
we will create an .env file which will contain our secrets, that is to say everything that goes
be connected to the database, by example, or later, secrets of ODE. So, the environment variables that we are going to
put in, it's going to be Postgre, username. In my case, it's Codecake. The Postgre, password, I don't have one. The Postgre URL is my localhost. And the name of my DB, Codecake, too. So, for my part, Postgre,
it runs with Docker locally. But you can use any Postgre. This .env file, we will have to
add to our configuration When we are going to run our server. So I'm going to modify my configuration, so
in IntelliJ, I go to Edit Config. I do Modify Options, Variable Environment. And here, I'm going to add, so I'm going to
remove it so you can see, but I will add here directly, my .env,
I do OK, I do Apply and I do OK again. If you are with VS Code, there is
also a possibility of adding environment variables. When you change the configuration of
your launcher is a little different but you can do that too. This .env file we will add it to
our gitignore to be sure that we will not commit it by mistake. So I'm going to initialize the git repository
here we are going to add it, it is well ignored. Then we're going to go to resources we're going to
rename the application file being reported in yml since we want to use this style. This file you will also have to
recover because there is still quite a bit of options that are inside and
we are not going to copy it by hand because it There is little added value in making you do that. I'm going to copy and paste it but
you have the link in description. In broad terms we have a The level of
logs, we have what is the active profile by default, the name of the application, the configuration of the
data source with the configuration also from Spring Data JPA, a little more Spring config
Data JPA with also the Hibernate config, we also have the config here from Liquibase, which we
will talk just after, and a config here including We'll talk again when adding the music. We now move on to adding certain
dependencies that we could not add in the Spring Boot Initializer, we go to the
POM and we add the following dependency which is going to be the org.springframework.boot and which
is called Spring Boot Starter Validation. This will allow us to validate
DTOs as input to our web service. Then we will also need
Mapstruct which is a DTO mapper. So it's... It's org.mapstruct and it's called
Mapstruct and it is version 1.5.final. We have the same thing, but the processor
which we will have to add to the compiler. This one is in scope provided. So. Next, we need the plugin,
we need the compiler. So the maven-compiler-plugin. The version is 3.11.0 and we
needs to manage configuration. So the source, java-version, the target,
it's the same and we're going to have to add a annotation-processor which will be our Mapstruct. The artifact-id is Mapstruct-processor
and the version is the same, 1.5.final. That's it, we update maven and we should be good. We now move on to the configuration
of Liquibase which will go through the configuration of our diagram. We already have the thick changelog file, it's
perfect, we are going to create an XML file that we will call master.xml and which will contain
all Liquibase changelog files. Once again, since it doesn't have a
big added value of getting hit that, you can retrieve it directly
in the description via my GitHub repo. I'm going to copy and paste it too. Nothing special, we made some replacements
of properties so that it is a little more compatible with Postgre and we will include
all our changelog files for our DB. We will create this XML file right away. Like this, .xml, and this
file will come to describe... Our schema and Liquibase will take care of
transform this XML file into an instruction SQL to create our database. Along the way, he will also do checks
on the fact that we have not modified this file XML and it will also look if it has any
new XML files to execute when we will make updates to the database. Our database is
architected as follows. We have a User table which contains
our user's information. We have a Song table which will contain
display information such as cover, the title of our song. We're going to have a Song Content table that will
contain just the binary files, so the binary file of the music itself. And we're going to have a Favorite table
Song that will contain the information which user appears on. For the rest, we have the creation of
sequences for our Song and User tables. Now it's time to try
to run our server. We press Play like this. We have an error and indeed, it
We need to create the Spotify Cloud schema. I'm going to go to my PG Admin. I'm going to go to Codecake and I
I'm going to create my Spotify Cloud schema. We save, we restart. So there, it seems to have gone well. We will nevertheless verify that
our tables are well present. That's it, so we're good to go. Now that our database schema
is done, we will be able to move on to the game writing our Hibernate entities, i.e.
writing our functional area. The application, on the backend side,
will be divided into two parts. The first will be the catalog,
which we will call the context catalog. And the second will be the user context. The context catalog, it will contain everything
what is related to songs and music. The user context, it will contain
anything related to users. We start by creating the user context. We go to the Java folder, we create a new
package, which will be called user context. In this package, we create another package
which we will call domain, and we will create our first entity which we will call user. This class, we will note it, arrobas entity. We will also add arrobas table. The name of this table will be Spotify user. This entity will have an ID. This ID, we will note it, therefore at arrobas ID. We will also add the arrobas
generated value, with a strategy of it is sequence type, with a generator
which is called user sequence generator. Then we need to define
this generator, its name. It's the same so user sequence generator
name of the sequence in base it is user generator and the size of the rental that we put is 1
finally the name of the column is id then this entity it also has a last name with a name
column last underscore name it also has a first name like this she also has an email
has a subscription this object is not yet created we will do it just after it is an enum and
finally he has an image url which is his avatar on will create subscription which is an enum and which has
like value premium and free we return to user and we will generate the getters and setters of which
we need then we are going to use a class abstract which will be used by the user
and which will be used by the user and which will be used by all our entities
this abstract class will allow us to carry out an audit to do this we will create
a shared kernel package which will allow us to put things in common through our
contexts in this package domain we create a class we will call abstract auditing entity
this class will have two main fields which are going to be an instant which is going to be createDate
which will be equal to instant.now and which will us allow you to keep track of when
our entities were created and for the second field when they were modified. On this field we will add an annotation
createdDate and at column with an attribute therefore name which is created
underscore date and which is not updatable. Then we have a second field which is also
a moment and which is called last modified date which is also equal to instant.now which this time
has the notation last modified date and And the name of our column which will be LastModifiedDate. We can create our getters and our
setters for these two fields. And we will also add annotations
which will ensure that Hibernate and Spring Data JPA understand that it is necessary to
the persistence of one of our entities, they come to supply these fields if necessary. These annotations are as follows. We have ArrobasMappedSuperClass and we have
ArrobasEntityListener and you need it pass an AuditingEntityListener.class. Finally, last thing, it is a class which
is generic and implements Serializable. And we need to have an abstract method
of type T which is called GetID and which will be implemented by all classes that will extend. Okay, this last one. So obviously, we have to
also add that it is abstract. We can now extend the
audit class by our User class. It is abstract of type Long. Alright. Now we can move on to the Repository part. We will create a new package which
is always going to be in UserContext. But this time we are going
create the Repository package. We create a User Repository interface. This interface is in
JPA Repository, User and Long. We import User, our domain, and we're good to go. Next, we will also create our output objects. Like this, it will already be done with our mapper. We will create a User Context Mapper package. This mapper is also an interface that will
be called User Mapper, which has an annotation Mapper, that we take our Spring model in
the purpose of being able to use Spring injection. The first mapper that we are going to put is
ReadUserDto, which we'll create in a minute. The name of this mapper, we will
call it ReadUserDtoToUser. It will take as parameter
a user who is our entity. This is for Mapstruct. That way, we won't need... to map our objects by hand. Next, we will create our DTO,
which we will call ReadUserDto. This class, sorry, it's a record. It has the following fields. FirstName, LastName, Email and EndName. Image url for the avatar we can return to
our user mapper does the import and we are good for the moment we will miss our user service
and at the moment we don't need very much now we can move on to the context catalog
so we create the text catalog package we create the domain package and we create our different
entities so the first is song we implement serializable we add the notation outside the base
entity and non-table based notation with name of our table which is going to be song song it is going to have
the same type of ID as user so we will copy it here to go a little faster we are going to change
here it's song there too it's song and there too here is our song it has a uuid which is a
public id, the column name public underscore id, it is not nullable, then it also has
a title which is similarly non-nullable, she also has an author, she has a cover and he
there is also the cover content type, story of simply know the extension of the image. We have all fields, so we can generate our getters
like this And we can move on to the next entity. We will create song-content in the same way
implement serializable we have the notation outside base entity, and with the name song-content,
this time, there is no id generator, since we are going to use a foreign system
key between the two, therefore, song-content, and so we will create the entity of the public column. content will use the ID of the song as if
it was his primary team so it's in low ID and in the bottom column name song ID if we are going to have
a one to one relationship between the two will tell hibernate that it must use the
same IDs for these two tables one to one and in join column name it's song ID and column
reference it is ID then we will have the content of the file itself of the sound which will be
an array of bytes we will note with glob on will store them in the database and the name of
the column is file it's the same it is not nullable finally last field we will have the file
happy guy like that we will be able to manage the different types whether mp3 wave etc name
and its files content type then we create our getters we can be able to manage the different
types whether mp3 wave etc name and its files happy type then we can move on now
to the creation of the last entity which is the entity to manage the favorites we therefore create the
java class with favorite we reimplement that there is disable we add the Table entity notation
name favorite favorite song we add a UUID which will be the song public ID which is its
ID and here we are going to have what we call a primary key composite which will be composed of the of
song public ID and user So that's also its ID and the name of the gala-user-email column. We generate the getters and setters
and we are missing an ID annotation here class where we will specify our composite
primary and which therefore must be created. We'll call it favorite-id.class. It doesn't exist, we create it. Yes, so it will implement Serializable. It has a UUID, a song-public-id,
a string-user-email. It has an empty constructor, it also has a
constructor with two fields and a getter setter finally we will also implement equals and
hashcode for this class we now pass when creating our repositories we create a
package repository we then create the interface song repository on extend jpa repository with
song and long here we add no method we will do it gradually when we need it
create methods then we will create our content repository like interface same on extend jpa
repository on Of type its content and long and finally the favorite repository which will extend the same jpa
repository that's it for our repositories next we create our output objects so we begin
by creating an application package and we will create a package value object in these value object on
there will be two called song author which are records vo yes adds which must
be the field it must not be blank and we will call it value then we will have a song
title vo which is also a record and which should not not be blank also that's what we're going to use
a little later in our DTOs we will create a another package which will be called DTO and which will
contain our input output objects first it's going to be we're going to create a save song dto save song
dto which is also a record so we must will add our value object so the song title
here we are going to move on to the line it will be simpler it must also be valid song author vo
then we will have the cover which is a painting byte here we have the cover content Type which
cannot be zero either and which is a string the cover content type and then we have the
file which is also a byte array therefore which is the sound file and finally we have the same title
that the cover we have the character string for the happy type of the sound file here we have the character string for the content type
of the sound file here we have the file of the DTO page This one is much simpler,
it's going to have a UUID which is the public ID and it will have a byte array of type glob which
is called file and just the file content type. The object will be used to be able to return
the content of music for our heads. Save this one, it will help us to persist. And now we are going to create the object that will give us
allow you to just read the information. We'll call it readSongInfoTTO. This one on the other hand is a class. It will then have songTitleValueObject
which is called title. She's going to have a songTor, the same. We're going to have a cover, which can't be zero. We're going to have a happy thong
type, which cannot be null. So be careful, it's cover content type. Then we have... Boul�en, read, and we have UUID which is the public ID. We generate the getters and the setters. We are good for this DTO. We only have the favorite left
song DTO and we will be good for this part. So the favorite song DTO. This, a record, is not zero. So we have a favorite Bouleen,
a UUID, public ID, which is him. Must contain, then base, zero rating. So we are good for the DTO part. Now as for the part
previous, we will create the mappers. In the application, we create a BackageMapper. With, as an interface type, we create a SongMapper. Notation, map. We are not going to add it because
these are a little more complex. We will see them during the service. And we will create the SongContentMapper,
which is also an interface. We'll just note it. Last thing regarding the persistence part,
we will have to configure Spring to indicate to it where are our different repositories, domain, etc. To do this, we will create
an Infrastructure Package. And in this Package, we will
add another PackageConfig. In this PackageConfig, we will add a
class that we will call DatabaseConfiguration and which will contain our annotations which
will allow you to configure Spring. We add ArrobasConfiguration. We add EnableJPREpositories. This annotation will allow
be able to configure in which Package are our repositories that we created. In my case, it's frCodeCake. SpotifyClone.UserContext.Repository. I also have another one in CatalogueContext. We also need to enable transactions
and we also need to activate the Audit part. We also need to activate the Audit part. We spoke earlier. With all that, normally,
we should be pretty good already. So, next step. Now let's move on to the part
where we will configure our Auth0 tenant. I invite you to open a browser on
the following URL, so manage.auth0.com. If you don't have an account,
I invite you to create one. And so I, for the occasion, I created a
new spotify clone tenant we will start with create a new application so by going to
the applications menu we create application we select regular web application we go
call it spotify clone we click on create you can go directly to settings then
in allowed callback url we will put both following addresses therefore http localhost 8080 slash
login slash hot 2 slash code slash octa we go also add the address of our front end
which is on port 4200 now we will also add the logout url if it will be our two urls
to disconnect we will also set the allowed web origin so that there are no worries
will also put the allowed web origin so that it there is no problem in Corsica and it is
everything normally for the configuration of our domain so we press save to save
and now we are going to go to the branding part since we are going to personalize a little bit
even a lot our login so that it looks like as much as possible on spotify so we will go
in customize options so ok enlarge a little a little so that we can see better so in colors the
primary button let's change it primary Button label it's the same thing the secondary button
border it's not the same color the secondary label button it's the same the base focus color
it must also be green the base over color it's good the link focus component it's not blue
but it has to be green and From Spotify. The header must be white,
since in fact, afterwards, we are going to change the background, and the background it will be black. This is why we reverse a
a little bit of all the colors. The background widget, the border widget, it's good. The input label placeholder,
it also changes a little bit. The input fill text, it turns white. The input border is the same. The input background turns black. Icons are the same thing. And just, success. We're going to save that already. Now we're going to go to font. In fact, we are going to add the noto font
100, just to be the same. At the level of our border, here, it's this one. It's a pixel. And the rest is good. Then, in widget, we will add
the icon and we will put that of Spotify. We can even enlarge a little there and the
patch background we are good look if we do try so there you see that we have a look close to
spotify ok now let's move on to the part configuration of our back-end to do this we
will go to applications again we click on spotify clone and we will copy
our client id and our client secret so I return to my back-end project I click on
my env point therefore my file and we will add two keys we will add hot0-client-id I will
copy paste mine and And I'm going to do the same for the secret shopper 0 secret shopper now
that I did that we go to the application point we will have to pass our values into
Aspring Security and the octa package so I type octa o2 issuer therefore the issuer is the address of
your holding for me is I can take here https colon slash then here is the client
id if I pass the environment variable so for me it's hot 0 underscore client underscore
id and the same for the hot secret client 0 client secret ok now that that's done we're going
to be able to configure spring security we will go in the infrastructure point config package and
we are going to create a java class called security configuration of this class we will add to it
configuration notation and it will have a unique method which will be called configure which
will return a security filter chain we will note it at the bottom of this pin since it is a good
spring this method is called configure it takes http security parameters and they will be
exception at the level of our parallel we will start by listing the urls that have the right to be
called publicly and the requests that have the right to be who must be authenticated
we will therefore use the object authorize authorize a At line .requestMatcher type HTTP method
get and the address of the endpoint, it will be API songs since it will be the endpoint which will allow
our users to be able to list the songs. So that's OK, it can be public. Then, the second method which
can be public, it's search. We will allow our users to be able to
search for the songs they want. That's it, so it's the same
thing in my permittals. And for the rest, all the others
requests, they must be authenticated. Next, we will configure the CSRF. So CSRF, we're going to do .tokenRepository. It's going to be cookie based. There you have it, with HTTP only. And we will add a tokenRequestHandler to it
of type CSRF tokenAttributeHandler. There you go, all that is configuration. To configure the CSRF token,
on the Angular side, it's also automatic. We configured it when the time came. For the rest, we will configure Spring. In OAuth2 mode, we will tell it OAuth2 login,
we're going to put a withDefault on it, we're also going to call the OAuth2 server resource with a
object so OAuth2 and OAuth2.jwt withDefault and finally OAuth2 client withDefault, we don't have
All we have to do is return our http.build, that's it That's it for our security configuration. Now we are going to register our users
in the database when they connect, for that we will have two different cases
first is if we don't know them we simply saves them and the second
case which will be a tad more complicated It will be necessary to update them if necessary. So to do this we will start
by creating a method in our repository so we will go to user
context, repository, and user repository. We're going to need an optional method
so which returns us a user and that's fine be findOneByEmail, this will allow us
to search our users by email. Once we have that we will create a new
service class in an application package, user service, we will This service will have we
we will need the user repository and we will need the maper it can be final and
that's why we're going to need utility methods the first will be a method which will map
a user with high formalism now we will be able to write a method which will map
the content of the jwt token which comes from high zero use that we will be able to persist in
our database this method is private it will return a user to us and it
is called high map of attribute to user it takes as parameter a string map of objects
and we're going to call it attribute we're going to import we're going to call it we're going to instantiate our user in
doing a new and now we are going to extract the information one by one dot get sub we go
make string username equal null if attribute point get preferred username is different from
null then username equals string of attributes We are going to do a to lower case then we are going to do
an if attribute.getGivenName and different from null user.setFirstName we will cast to string
and we're going to say attribute.getGivenName there you go otherwise attribute.getName not equal to null then
user.setFirstName always the same we cast to string attribute.getName here we go
make an attribute.getFamilyName and different if it is different from zero then we make a
user.setLastName of string attribute.getName then if attribute.getEmail is non-null
we make a user.setEmail we need to cast it string let's do the same .getEmail here small
Subtlety we need to make a sub.contain type and username is not null and
username contains there if this is the case we make a user.setEmail equals username If it's still not
in the case we make a user points this email equals sub last block we do an if tribute point get
picture it's different no one finally user points 7 a.m. url is in thong and we make an expensive get
All we have to do is return our user and we are good for this method which was quite daunting
now we're going to need a method that will allow us to update a user
if necessary then this method is private and we will call it date user will take as parameter
a user we will fetch our user in the database data user to date optional we will call
our user repository using user fields points get email if this user if we get to it
find we will first extract it from this way user point get email we will first
extract it like this user point get user everything is dated here and we are going to update the fields
who interests us?bije mail user points and email that outside everything update posit image url north
get mastered l user 17 las namen use Get Last Name killer says it all point on test the usources points
busca ister once we have that we save we save an arrow and we are good there
method according to We will have to arrive at extract via Spring Security the information which
are in the JWT at the time of the request. To do this, we will return a
readUserDto and the method, we will call it getAuthenticatedUserFromSecurityContext. The object is of type ODEuser,
we'll call it main. We're going to cast him since it's not going well
be the case in Spring Security. We will call the securityContextHolder,
we are going to do a getContext, a getAuthentication and a getPrincipal. Then, our user will
use the map method. We will give attributes since, like
you can see it, the getAttribute, it's a map of string object and
This is the parameter of this method. It will return us a user and then,
we will be able to use our mapper since we are going to transform a readUserDto. Sorry, we are going to transform a user into readUserDto. Small font, here I put
update instead of updated. Now let's move on to writing
of the endpoint which will allow the front to retrieve information from
users when they are connected. To do this, we will create a package in
the userContext which we will call presentation. We are going to create a Java class that we are going to call off. OffController, or rather offResource. We will note it with restController and offResource. With request mapping the address will be slash and
Pierre and we are going to need our user service that we are going to inject it and we are also going to need
of an object called clients registration we're going to need this one for the
lockout on this we will generate a constructor and we are going to make our first endpoint which will
be that of retrieving information from the user who is logged in this method
it will return a response entity of type read user dto it will be called get authenticated
user and as parameters it will return a note she will take in parameters sorry another
user which will be annotated at authentication main that will allow spring to us
inject it when calling this endpoint endpoint it will be annotated at get mapping
and the address of this endpoint will be slash get authenticated user simply the body of
this method we will do an if obviously it the user must be different from null if it
is equal equals zero we will return a new response entity with http status internal server error on will retrieve the information call it user from
authentication and we will do a user service Point get authenticated from security context
this way we recover the information that are present in spring security so there we
returns response entity point ok point audi du user from Authentication and there we are
good next method this is the method to be able to disconnect our user therefore
this one will return a response entity we will call it logout and it will take parameters
http servlet request we will call request on will note it post mapping and the address will be
logout we will first call the registration with a get provider detail point get issuer uri we go
create the variable then we will create another variable origin url which comes from the request
http header point origin we will make a table of objects which will be called param which will be issuer
uri registration point get client id and origin url then we will have to format a message message
point format sorry message format we will import it's going to be 0 and this method has the deparameter here
that's why there was a little problem so v2 slash logout client id equals return to
equals 2 so that's it to disconnect from o2 then we do request point get session
point invalidate and we return response entity du point ok point body map of logout url logout
url yes because I have to create it here variable called logout, we're good
for this method then small piece of code missing little typo here we need to inject a
client and Ce registrations, here it must be equal to findRegistration, which must be equal to Octa. So. If we reduce here and try
to run our server normally, everything must go correctly. There you go, it's perfect. Now, we come back to the front-end side
and we will configure it so that it can communicate with our back-end. Then we will make him consume both
endpoints, logout and getAuthenticatedUser, that we created just before. First, we will configure a
proxy to avoid CORS issues. To do this, we will create a file
which will be called proxy.conf.mjs at the root of our project. In the latter, we will put an export
default, an array that will contain an object and which will have several fields, therefore the field
context, where we will specify the addresses, which he must proxify, therefore o2 and slash login. The target, for us, will be our back-end,
so http://localhost 8080 and secure-true. That's it for the conference. Now we will find ourselves in the angular json. We will look for the dev-server line. We will add option. Like this, option, it's an object with a
build-target field which will be spotify-clone. Colon, build and the proxy-config attribute,
proxy.conf.mjs, therefore the name of our file. There you go, our proxy is configured. I need to be able to run my
server in the normal way. This is perfect for the moment, we will minimize it
we can close our files we will head now in the app config in the app config
we will add if I move a little bit to the line if too and I will add the http client
like these with the configuration of the xsrf token if therefore the cookie name xsrf-token
and the header name is x-xsrf-token like that it's done, we don't have to think about it anymore, we're fine
allow to inject the http client now On will generate our two environment files for
be able to manage our different backend addresses. To do this, we go to a terminal and
we will type ng g generate environment. This will generate two files in a folder. We will add In this one, Point URL API,
we're going to put slash slash localhost at point 4200. We do the same thing on the other side. There you go, our environment is configured. And we will now move on to
creation part of our model. So, we will create a service directory. And in this service directory, we go
recreate another directory with template. In this model, we will create a file
TypeScript which will be called user.model. This file will contain a
interface which will be called user. And which will contain a first name of type
string, a last name of type string, an email of type string, a subscription which will be
of type subscription, we will create just after, and an image URL of type string as well. We will create our enum. Subscription here it's premium and free so that's it
for our model we are good we will be able to pass in the service part we will generate a service in
the service folder which will be called off there you go we open our service in the latter we are going
inject the http client like this we will also need rental is an angular service
we will create a variable not connected which will serve us a little later before writing our
two signal and our fetch method we will have need an interface that we will use between the
service and components this interface we will create it in model and it will be called state
the code for this model is in the description for I'm just going to copy and paste it, there's no
of particular value to what we type to them is just an object with value error status fields
and then a builder class which therefore allows of builder for errors for successes and
for initialization once it's done Towards our service and we will be able to instantiate
our first signal that will be called fetch user or dollar which will be private and which
will be of type writeable signal of type state user http error response we will import all that
so it's not popper jscore but it's our slash state pattern which is going to be equal to the line
to http user signal state point builder error response for success email and six dots
not connected small typo here fetch user so the second signal which will be our public signal
it's going to use computed and it's just going to derive it from fetch user dollar then our fetch method
it will return void and inside we will make our call http get or get which is going to be from
type user the address it will be dollar environment dot api url slash api slash get authenticated
pull user then we can do it directly subscribe inside this subscribe we go
manage the next case and we will write in the signal by making a set of state point builder point for
success user http error response for success On send the user inside and we do a build. Now for the error case. There, it will be Error. We will have to do this on several lines. So, we're going to put an if. The status of the error. Ah yes, the error status. We're going to type it here. It is equal to HTTP, Status Code Unauthorized. And the user is not authenticated. We'll create that in a moment. So, we're going to write to the signal. Almost the same as that. Except we're going to send email. We will send a ThisNotConnected. If the error is anything other than that,
so we're just going to return an error. State Builder for Error. And we return the error. Now the IsAuthenticated method. It takes nothing as a parameter
and it will send a boolean. We are going to make a if. This.FetchUser. So if there is... A value in the signal. We're going to go back. This.FetchUsers.Value.Email. And we're going to make sure that this email
is different from NotConnected. If it returns something else, then it returns false. Now, the Login method. It will not take any parameters. It returns void. And we're going to do a... Location point href which will be equal to location
point origin dollars with brackets this location point prepares external url and the url that
going to be high slash permission slash octa that's all we have to do for the
login then the logout is the same it sends void we will use the http client to do
post the url it will be $environment.apiurl The body it is empty and we will have to specify a
small option to be sure that we send correctly cookies even if it is not the same domain. We go to the line, we subscribe, we go
put the next case and in the next case we will do a this.fetchusers.set I can directly copy
that since in fact we are going to return A user simply not connected and then here we
will make a location href equal to response point logout url and I forgot the response here which is
of type href Issued in this case so we are good for our service little forget above at
signal level wears out it is necessary to add a build point so that there are no more errors
compilation now we can do a little checking and running our angular cli server
to check that everything compiles correctly here so we're good now that we've finished our
authentication service we will be able to move on to display part our task will consist of creating
one and hours as is the case on spotify with before after and the login and sign up buttons
this way we can manage the authentication displayed to our user if he is logged in or
not to do this we go to our terminal we go add by composing in the yachts that we are going
call and hours this component we will open it we will start with the ts part we will begin
by implementing at the limits because we are going to need it we will also so as not to forget it
import the fontos are modules because we are going need to display icons next side
template we will also need the offer of Service we are going to inject it we are going to create an object
connected user be able to know when the user is connected that we will initialize with this of
service.notconnected and we will also inject the location service we will now instantiate
an empty constructor we will write instead in this constructor we are going to do effect and in this
indeed we are going to go and see if we receive a value of the fetch user signal if its status is
equal equal to ok then there is a small error in the service here so in fetch user must go
so on this line and I forgot the small ones parentheses that's it because otherwise we don't have access
to the object and the signal now I can do my status so I have my if and in this if
I'm going to make a this.connected user and I'm going to assign it since everything is ok to my of service
dot fetch user with the parentheses and I will recover the value that's it we can go there seen
that in any case it can't be undefined once I have that I will be able to write my
login method which will send void which goes all simply called of service point login this
method for callback It will just redirect my user towards the back end so that the back
end redirects it to 0 Or authenticates it next in the case we will write logout if we simply
do a this point service point logout and we will also write for the two small arrows so
go backward is the same it's void and this time we just do this point location point back
and for go forward same void and this point location point forward finally last little
thing we're just going to raise the ng a little bit we init we will call off service point fetch to
retrieve our user's information and we are good for our service for our
sorry component we can now move on to the html part so we will open the header component
point html we delete everything that is in it and we will put a nav tag which will have a
attribute class so rounded top 2 navbar sticky top background dark z index 1 and padding 2 in
this navbar we will have another div which will have a fluid container class another div which will
have a deflex class justify content between align item center and a width of 100 still in
this last one we will have another div Which will have a deflex align item center class in this div
This time we will have a click we will call go backward because this div will be used for
show the arrow and it will have an attribute class which will therefore contain margin end 2 Rounded
5, Border 1, BG Black, Router Icon, D Flex, Align Item Center, Justify Content Center, sorry. And in this div, we will have our Fa Icon which
will be called Chevron Left with a size of 2. Then another div which is going to be this
this time the Forward which will be called Go, the Go Forward method, with the same
classes, apart from the Margin End. So here, we will simply copy. And then, the Icon ending which is called
Chevron Right which has a size of 2. Then, here, we're going to open another div. Inside, there will be a If. And in this If, we will have an OffService.isRef. If this is the case, we will create a
another div with class D Flex, Justify Content Center, Align Item Center. Inside this div, we will have a class
OverAnimation which will contain an A tag, which will contain a click with just login. Quite simply, a Margin End 4 class. And this link will be signA. Then we will have another link with a
attribute so class with btn light button and over animation we will add the click with the
login method and the text of this link it will be login then at the else level we will put that
this is if our user is logged in if it is connected we will have a btn-light button
over animation with a margin end of 2 and click level this time we are going to call logout
and here we are going to say logout below we are going to have a link with an if if the user has a
avatar then we will display it in a tag img with src so we will make a connected user
point image url we will put a small attribute alt so user avatar class there it will be we will
have width 35 and height 35 also if he does not have an avatar then we will display an
generic icon which will be called just user with an avatar-unknown class here we are going to add
a little bit of css since there are classes which are not bootstrap classes so we have
for example router-alte Or the size font is equal to 10 pixels we also have here the width which
will be equal to 30 pixels light it's the same we are going to have padding top at 2 pixels the cursor
point to get the glove we're going to have a class avatar with a border radius of 40 pixels and
an unknown avatar with a border radius of 40 pixels a font size of 22 pixels a width of
45px, a height of 45px also, padding-top 4px, text-align-center, vertical-align-center,
and background-color, we will put white, and color, these are variables, so we will have to
that I add the import of our variables. For this, I have to do sets, slash
css, and variables like that, like this. Ok, so now if I go to the side of my
browser, I should refresh like this, but yes, nothing is displayed, this is normal,
since I have to add my header here, so I say header in this way. So the icons, they are not displayed
not very good because you have to that we import them, which makes sense. It was chevron-left, chevron-right, and user. Now we have our two cool buttons
now here we don't have the over of the buttons and this one does not have the cursor pointing we will
fix that right away because I forgot a css class we are going to do over animation over
and we will add transform scale 1.09 there you go so with that we should have the effect here
we have the effect with the sign up ok now what we can do to not bother too much
will add here cursor pointer and normally we should have here the cursor with the problem
the problem is resolved we will be able to check if the login works before that, make sure that at
level of your server is running well your back end so if necessary you have to restart it now
I'm going to click on login so it redirects me well I don't have an account so I'm going to click
on sign up I will enter my information here I click on continue I accept here
that here I am well connected so it worked correctly now if I log out there is
a small problem then we have a small typo of the side of the back end so in log out there is client
id here which must be client underscore id so we will restart our server we will try again
and this time we are good so if I click again on login I don't need to log in I
I'm going to try again to reconnect once there and then it works fine I log out
another check that we will be able to do is check in the local database that our
user is present I can go to my workstation is free and I can view data all rows
so you can see that here I have my information Now let's move on to the part
importing a song via a form. We will start by creating our model. So we go to Service,
Model, and we will create Song.Model. We will start by creating an interface
which will be called TitleValueObject and which will contain Value. It's going to be a thong. Then, we will have another interface
which will be called AutorValueObject and which will also contain a Value. We are going to have yet another interface which
will be called SongBase and which will contain all the basic fields of a song, at
namely the PublicID, the title, the author. So, sorry, because the title,
it's obviously a TitleValueObject, and that too is the author. Next, we will make our interface
which will allow us to save a song and which will extend SongBase. This interface will have a file. Which will be of type File. It will have a ContentType file
which will be of type String. A cover which will also be of type File. And a ContentType cover which
is also of type String. That's it, we'll add more, but for
the moment, we can stop here. We will now move on to our service. So we go to a terminal and we go
create a service by doing nggsService. SlashSong. We open the service file its service points
and we inject our client http we create our private signal like these error is what think
why can't I import but because state the import is not good there we will go to the
line and state point builder which will be of type save song http error response for unit mobile
then we will have our ad signal which will be a had head conspiracy 10 points had with a
dollar we can now write our service had which will return void and we will make a 10
points http point save song post the address that going to be environment point and stone and euro is
slash and stone slash are we will have to send a form of achievement that we are going to create we are going to create it
immediately above besides had it is necessary that they take a As parameter, a SafeSong. We will create our FormData. Since in fact, the data, we are going
send them in good multipart. We create a new FormData. I'm just going to check that
import is definitely the right one. I think so. So, we do an Append. We're going to do a first Cover part. We will put the file of
cover of our song. Next, we will put the Song.File file. Here I can tell him that anyway,
it will not be Undefined since the validation, we will have done it just before. Next, we will have to clone our object. We're going to make a StructuredClone of Song. Because in fact, I'm going to remove
clone both files to be able to send the rest of the information in JSON. I can just do a FormData.Append
with the ADTO and I will send the JSON as a string. So. I have my FormData. Now I can subscribe and take care of
case when the server sends me back my SavedSong. So here, I'm just going to do a sys.add.set
and we are going to make a state.builder of type SavedSong.http.errorResponse
and we're going to send the SavedSong back. Yes, because, little typo,
It's a great success. So. The error case will be,
we're going to copy this line. We're going to say forError and we're going to
put an error like this. We will also create a reset method in
the goal of being able to reset our form and content of the addState signal. Builder when we want it safe song http
response point for unit build here we are good for our service now before moving on to
the actual writing of our form we will need a way to be able to notify
our users throughout our application easy way so we will create what we call
a toast service which will allow us to display notifications anywhere thanks to
a simple Angular service then we go to our terminal we add a service which will
to be called toast we add a table to it which will be called toast info which will be a table which
will contain our toast notifications info it's an interface so we create everything from
following the toast-info.model model we write export interface toast info it will contain two things
the body its body and a class name now we can import it and we will create a show method
which will trigger the display of this toast it will have two parameters which will be
type type and body so type he will have two values at the time of display of this toast it will
have two parameters which are going to be type type and body so type will have two values as possible
success or danger and type it will allow us to decide on the class name so we will say that
if type equals equal danger then class name is equal to bg danger and text light otherwise class name
will be equal to bg success and text light whatever we going to use actually here it's The toast service
from ng bootstrap so we what we do we write right here is a method that will allow us to
make things a lot easier but otherwise we simply use the booster toast
then we're going to call our we're going to add a pardon element in our table so it is a
toast.push of body then we will form our object which will be toast info equals body and class
name here it is and then we just have to do we're going to just do a this.toast.push of toast info all
simply now that we have our service we will be able to go to our app component in
firstly we will go to the ts and we will inject our toast service like this now
we go back to the html and we will add a div here with an absolute position class z index 2
margin top 3 toast container we are going to make a fort this fort he iterated on the table which is located
in toast service here it is and we're going to call our ngb toast which will have a class which will be equal
at toast point class name we just want to close the tag to have auto completion here it is
then we will have the self-help which will be equal with holes we are going to have delay which we are going to set at a
second hidden we will call toast service point remove toast that we will have to create it and inside
we're just going to add toast point body we're going to create this method immediately takes toast
and inside we are going to make a zis.toast is equal to zis.toast.filter toast and different from toast that's it So now I should be able to test if
my toast works by doing a HelloToast show. That's it, so let's try. There you go, it worked well. It's just not very well placed. We need to add at the level of
SCSS the ToastContainer class which will contain bottom 13% and right 35%. With that, normally, he
HelloToast should appear. Small font here too, it's a success. We will now be able to move on to the part
creation of the form itself. Firstly, we
will create our component. We're going to call AddSong. Then, we will open the .ts file. We're going to clear out a little bit. By the way, we will... Before attacking the code,
we are going to create an interface. This interface will be useful to us
validation for our form. We'll call it addSongForm.model.ts. We are going to do a type export. We'll call it createSongFormContent. This guy has a title which is
FormControl type with a String type. Then we have the author who is the same,
of type FormControl, of type String. On a Cover which is a
FormControl of type File or Null. And File which is of type FormControl,
the same, of type File or Null. Now we move on to creation
of our form on the code side. We will first import ReactiveFormModule. We will also need
NGBAlertModule and some icons. So, we will also import
the sum base modulates. We are going to create an object which will be called
songToCreate which will serve as our object which we will fill in as we go along
the user will fill in the different fields. For now, it's going to be an empty object. We will use the FormBuilder. We can also put this one private. FormBuilder. We inject. We will also need the songService. We will also need the router
to redirect our user once he has finished adding. His song. And finally, we're going to need... Toast service, we're going to need toast
service, we will see immediately afterwards why there has a problem, and two last variables, we
gonna need one, are we in mode creation, that is to say we wait for the return of
server, and do we also need to know the flow status, which is of the flow status type, which we
will create in a moment, and which will be equal at init, this is a type that we will create above,
type flow status, which can be either of type init, either of type validation file error, or
validation cover error, or success, or error. So actually it's because
I forgot that, there you go, we're good. I can pass now
when creating my form. public create form which is equal to
form builder.non-nullable.group, we're going to create another group. This group will be of the create song type
form content, and here I will be able to specify the name of my fields, form,
and I will be able to instantiate them. Value is like that, the field is
non-nullable, anyway, and the, ah yes I forgot, here it is non-nullable, and the
validator, we have it here, and this field is required. Then we're just going to copy the others
fields, since they are the same, that's it, anyway they are all nullable, if one
fields is null, in all cases... It doesn't work and we block validation. Now we will move on to writing the method
which will be triggered when the form is sent. This method we will call
create, it will send void. We will start by putting
the boolean isCreating is true. Then we will do some validations,
we will look at songToCreate.file it is, if it is zero then we will
display a general error message. We're going to do the same for the cover so
songToCreate.cover equals equals zero and same we have a flow status which has the right value. Then we will come and create our two objects, our
two value objects that are of type title value object therefore value and inside we will extract the
title that is in the form, value.title. We do the same for the author value
this.createform.value.author. We now fill in the fields
title with the value objects that we just created above like this and then we call
our service so the song service, we do an add and we send the songToCreate, that's it. Then we will need two methods
to be able to manage when the user he will come and upload the file so
song and cover file. To do this we will create a private method
Which will be generic between the two methods and we will extract the file from Du champ
upload we will call it extract file from target the argument is target which is of type event
target or it can be null and this method it returns a file object or null we type const html
input target equals target as html input element if this target it is equal to zero or
if the html input target point file it is equal to null then we return null otherwise we return html
input target point file and return the first file then we will take care of the cover so
on upload cover which takes an event as an argument target or null we create the const cover which will be
equal to This.extractFileFromTarget we pass it parameter the target object and we check that cover
is very different from zero if that is the case we can put in our object so songToCreate cover
equals Cover and we do the same for the content type to know the type of the file we do cover
point type quite simply we will do the same for I'm going to copy paste we do the same for
the file is an upload file that's file and here it's point file and there it's file content type
now we are going to go back to the top of our file and we are going to implement our component
destroy implement this on December a little bit here we are going to call six points are service points
that's the point of that is that when the component is destroyed we will reset the signal in its
borderline state it was the service we had written just before then we will create a constructor
like these and we will add an effect in the latter so that we can react when the
song has been created that we have feedback from the server the first thing we do we're going to put the
is creating a false since in fact this ball 1 it will be used to display a small icon of
loading to be able to signify our user that he has to wait then we are going to make a
if song service point add signal if status it Is ok I refresh and My list, so this one
we don't have it yet, so for the moment I I'm going to comment on it, we'll uncomment it later. Then I'm going to call my toast service
with a show and so I'm going to mark song created with success and it's a toast of type
success also we will call the router and we will redirect our user to home,
like this if there was an error, so I will test song service signal, if the status
it is equal to error so I will display a danger type toast this time with error
when creating song please try again occurred it's danger type we're good for the code
of our component now we move on to HTML now we are going to go to the file
app.route.ts where we will add our component addSong in order to be able to display it then
we add an object like this in the table route with a pass attribute the pass will be
slash add pulled song and the component it will be addSongComponent if we execute then if it is
already done just fine, otherwise you can run again your server we come back to our browser ah yes, so you just have to remove the slash
there you go, and if I press I have addSongWorks very good, now we are going to open the
HTML, we erase what we have here And we create our first div with our flex class
flex column and align item center. We create another div with an attribute
class, with a width of 75. We put a title with a class attribute
margin bottom 3 and a start text. And we put the title add a song, without spaces here. Now we will be able to write our form. So form with id create form ng submit form. We will call the create method like this and form
group form group our form is called create form inside this form we add a div with
the class therefore form floating a margin bottom of 3 secondary text colors our input
it will have therefore it will be of text type since it's the title it will have a form class name
control which comes from bootstrap we will put a Haiti title will go to the line at the level of
validation will make a class point is a no point is valid and this condition is created
form lookout point title valid points that's it this will add the class is valid if this field
is valid like that we will finally be able to stylize bootstrap not able to style and means to
the user if it is good or not for the isin valid it's the same thing we make a watch with
title and inside a point dirty dirty it is if the user touched the field then a cry
in the form of a lookout point again Title and now we check if it is invalid, this is how we do it
will also put a placeholder title on it and form control name which is title here is this input
it has a label for title title and at the level of our validation we will put an if create form
point get title point dirty and I'm going to copy it it will be faster point as error the error is
therefore required then we display a div which has a class invalid feedback and therefore we must
close this div and inside we will mark title is required here for the first input title on
can check here, we have our title well our input which remains correctly for me is
can see here icons which display well with validation now for the author field
will copy paste what we have already done like this and we are going to rename author here author author
like this author and author is required voil� I have my second field which is not validated
very good because the form control name was not not the right one and that's it, now it's good
we can go to the queue, then this one will be a little bit different, because he is type,
so there he says it's gone, and here we just go to have, well not to have an invalid, we are going to have
a placeholder, the form is done, we will have an accept which will have .wav.mp3, there we go
accept both, so we have the valid class, and we must also call change, change,
and we will call upload file with event.target, here we are, we are going to format a little bit, here it is
file, and file, there you go, and the label, sorry, the label we will instead put mp3, validation
here, the if, we don't need it, that's it, and we will take the block here, and here we will put
id cover, cover, type file, here it is cover, besides there it's file, there it's cover, cover,
here guys, we are going to put jpeg, jpeg like this, png, and .sg, so there we have that, we have good
cover, the change is on upload cover, and no don't file, and we should be rather, not bad, we
we have our fields here, very well, we have to here change the mp3 label for cover, we also have
a style part, which we will have to add, Here, to correct a small defect, at the level
upload fields, so we're going to put a little padding, we are going to put a padding of 9 pixels, 0,
0, and 10, like this, and an auto height, there you go, And once we added this
style, we need to add it to both file type fields. There you go, here you see, we have
fixed this place a little. Very good, we can now attack the
part with the buttons and the alerting part. We will therefore add a div with a class of
grids, a button with a button class, button primari, which is disable if the form is invalid
or if we have a song being created. This button is of type submit. And if we are not creating,
we're just going to put a div with add. And if a song is being created,
so we're going to put a div with a class. Flex align item center justify content
center with an icon that will be called circle notch and a margin end 2 class. And a little spin type animation with
a small div in which we will mark adding in progress with three small dots. We will immediately add
before forgetting the icon in fa. Circle notch, we don't forget that,
in our icon file. Now we can validate in our browser. There we have it, so
if I fill in my fields, Here I have my ad which works correctly which
is deactivated now we will move on to the handling global errors to our form
for example if something happened abnormal when we sent the information to
server then we add an if like this and we will test the flow status variable so if flow
status is equal to error or if flow status is equal to validation cover error or if it is equal
to validation file error then we will display a ngb alert is not therefore dismissible which is
danger type and which has a flex align class item center justify content center and margin-top 3
this ngb alert it has a fontosome icon with alert name and alert name
it has a class margin end 2 an icon circle x mark with a size of x here is this icon
we're going to add it right away, it's fa circle x mark like this then we will add a div
which will contain the different errors so if the flow status it is equal to validation cover error
in these cases something is wrong or something is wrong With the cover the same with the file
with the mp3 file and if there is a general error so when create the song now we will test
quickly for our form to send the data at the back end even if we don't yet have a head
point then I'm going to connect like this and I go to Add Song, I
I'm going to add some music, I'm going to open the inspector,
we're going to go to Network. When I do Add, obviously I have
a 404 because the endpoint does not exist not, but we can look at the payload. I have some binary here and I have my
DTO with my JSON, with the right fields to every time in my title, author, etc. Now we are going to head from
side of the backend to create our endpoint which will create our song for us. To do this, firstly, we will have to
fill and add methods to our mapper. We go to the SongMapper and in this
SongMapper, we are going to add a first method which will return us a song
which we will call SaveSongDTOToSong and which will take a SaveSongDTO as input. There are two fields. We'll have to ignore it. The first is ID and the second is the
public ID, since they don't are not present in the SaveSong DTO. So there is absolutely no point. Either way, it wouldn't be possible. Second method that we will need,
we're going to need to have a method which returns us a readSongInfoDTO
Which is called SongToReadSongInfoDTO. And who takes... Setting a simple one we just need one
mapping here a single notation with a target which is favorite since this part is
loaded by another table that's the same favorite part it's another table now
we are going to need small methods that will map fields that are internal so for
the value object exactly so it will be this method before sending a title value sound
object called string any title value object string title and it will return us a
new song title value object simply on will do the same for the authors because song
author value object are authors value object are authors and finally the same but in the other
meaning so this time it refers to and finally the same but in the other direction so this time
it refers to a string and there it is song title value object to string and it takes as input a
song title quite simply and we return title point value and we do the same for author copy
this method song author value object song author value object that's it and that's it and we do the same
for author copy this method song author value object that's just author That's just author that's it
note my fear and ok now we will move on to service part so we create a new class
java we will call song service in the package application we will rate it with arrobas Service
like this this service we are going to need our maper its maper we are also going to need the
repository therefore private final song positori on will also need the final song song content
repository and we will also need the song content mapper I create my constructor for
inject her four anvani mines a little bit the line and we just need to add a few
methods in the song content mapper then the first method it will return a happy song
dto and it will be called song content all its happy eto she takes parameters a song happy
we will also add the notation by base mapping which has as source song point public haiti
and the target public Haiti like these the second method it will be called it will return a sound
happy and it will be called save song dto tout her she will take a safe song as a parameter
like these for ap its content it's a table which only contains song files. We do this in order to be able to
request them in a different way. That is to say, we have a table for
information, a table for the content. This allows you to just ask for it
when we need to play music. While on the other hand, we have the other
need to be able to just display the songs. We return to our department. We will now create our method. It is a public method which will return
a readSongInfoDTO called create and which takes a saveSongDTO as a parameter. The first thing we're going to do,
we will call our songMapper and we will call the saveSongDTO method. Any song that takes saveSongDTO as a parameter. Like this, we transform
directly our DTO as an entity. This entity, we will persist
directly thanks to the songRepository. Here, we're going to make a song and we're going to create
on the fly a variable in passing. This way, we will have the information as
IDs and everything generated by Hibernate. Then we will do the same
for the songContentMapper. We will map with the saveSongDTO to song method. We will extract the binary data
and we're going to make a songContent. I need to create with the variable
here, songContent and we're going to make a songContent.set .setSong songSaved. Like this. Now, at the level of our songContent,
I will be able to call my repository and make a save of my entity. Finally, I can do, here,
I will recreate my variable. No, I don't need it. I can just do the return directly
with my songMapper.songToReadSongInfoDTO. And I return the songSaved. So much for our service. Now I can move on to the endpoint part
and we will create a package in the catalog context which we will call presentation. And in this package, we will create
a JavaSongResource class. This resource class, we will denote it with an atrobase
restcontroller and at requestmapping, with as argument inside, slash API. We are going to need song in this department
service, in this resource, sorry. We're going to need song service. We will also need
our validator, like this. We will also need user service. And we're going to need our object mapper,
which will allow us to deserialize our JSON. So we're going to make a new object mapper, like this. With this, I'm going to create a constructor. So I'm only going to need
song, I'm going to need this. Alright. My method will be public. It will return a response
entity of readSongInfoDTO. It's called add. We will note it. Arbase. Arbase.
Post. Mapping. With as value, slash songs. And she's going to publish. ConsumeMediaType.multipart FormDataValue
As an argument, we will have a multipart MultipartFileCover A multipart file that goes
be called file And a character string which will represent Our DTO, our saveSongDTO in
string We will just have to add small annotations So that's at arrobaseRequestPart
So that Spring can recognize the different parts of the multipart form We will also have the
requestPartNameFile We're going to pass this to the line Same for this part here Name and this part
is called DTO And potentially it can be IOException In the body of our method The
first method, this is the first thing method what we are going to do, we are going to Deserialize our jason
in doing so we will make a read value save song and we want to serialize it in save song dto.class
like this we will create a variable then this variable we will have to add the information to it so
we will have to recreate the information so cover and file we are going to do it this way we are going
save song point then little typo here save it song dto it's you have to call it title if we're going to
have a mapping problem if we continue we will put save song author it's the same this one it
it has to be called autor then we will have a cover point get byte cover point Point get content
type exactly then we will have a file point get byte and file point get content type now
that we have reconstituted our object we will validate its fields we will do as follows
we will call our validator with the method validate and we will play our safe song
dto we are going to create a local variable we are going to say that if there are violations we will remind them
violation if this set is not empty then we will create a string with all the errors that
that there is therefore in this time this object will do violation point stream point map of violation
and violation point get property passes over violation point get message to get the info
and then we will do a collection point joining like this we can pass it to the line and that
also for more clarity then we will create a detail problem with which will be called validation
issue and our problem detail we will generate it as follows for status and detail on
will send http status point bad request and in we will mark validation errors or details On
will pass the string that we built just above above in this way and we are going to return so
a response in titi of validation issue point build that's how we managed our error cases
as it is necessary finally the easiest passing case we are going to make a response in titi point hockey and
inside we will call our service Point create with our object that we created above there you go
we are good for our endpoint except that there is a few small typos so we will have to
go to song.java here there is a public id which is like this so we will have to factor in
a little bit public id like this if i try to recompile and in song there is also the fact
that cover it must be a byte array it must also correct the
setters and getters there you go in this way it's good two little things
additional which will prevent us from adding songs the first one we will have to
render in song.java and add the notation at arrobase lob .java and add the notation
atrobase lob it works correctly then it we'll also have to go to song
service to be able to activate transactions in the methods of our service once we
did that we can reload our server we can go to the side of our front normally
we press add and normally we must have the toast song created success what we
can do to validate and be sure we can Validate yourself and be sure you can make yourself different
thing to link Weiwei Poazar then we even have it fly ?? we can refresh here and so was going
in post yard and??we can refresh here and so go into post yard and look at the level
of our som confused our table song look at the level of our som confused our sound table Here we have a good line which has been added
we can also go and take a look of eye in its content to look we have
well here our files which are present now we will stay on the side of our back end
and we will create the endpoint part to be able to recover all the songs in the aim behind
to display them in our front we leave in our song service we are going to do a little bit of
empty like this we create our method which is a public method and which will return a list of
read song info dto so we import we call it get All, it takes nothing as a parameter. We're just going to add a little annotation to it,
transactionAll arrobas, to tell him that This is a read-only transaction. We call our songRepository
with the findAll method. We do a stream, we go to the
line, we make a map, we use our songMapper, the songToReadSongInfo.tto method,
and we then call toList, for just be able to have a list. We can make a return like
this, and we have our service. Then, we go to the resource side, so
from the endpoint, we write a new method which is public, and which will return a
response entity of list, of songContent. We import list, and we call it getAll. It takes nothing as a parameter and we return
simply response entity.ok song service getAll is read song info.dto We add it
getMapping notation with getMapping address Which is going to be slash songs and our endpoint is good we
check anyway that it compiles, our server is running correctly and we can now
move on to the front part for our front we are going start by writing the service part so we have
first need to modify a little bit our model so we're going to go to song-model we're going to
add an interface called read-song and which extends song-bass this interface it has a
cover which will be of type string we will have a cover content-type which is the same as string type
a favorite boolean that will be a little bit for later and a champ-display-play it's the same,
That 's it, we're going to talk about it again, that's it for our interface now we can go to the song service
we are going to create our signal we are going to call get-all which is going to be a writeable signal which is going to be
type state, type read-song stop type http error, we will now instantiate our
signal like this, we will go to the line and we will give it a state.builder which is of type
stopping readSong.httproresponse.forinit.build. There you go, we create a second signal,
this time public, which will be instantiated using computed, and we will
use the get, the private signal. There it is, like this. We can now write our service
here, so getAll, it will return void, and we call our HTTP client by making a
get type stop of readSong, like this. Get, we take a URL as a parameter,
environment.api.url.api.song. Then, we will subscribe to this observable. Next, when things go well, he will
send back a song table, and we just go, do a set on our cue
private, and pass it the shutdown state readSong with the song object inside. So, just like that, .success, like this. If there is an error, in these cases,
we will notify the signal by doing a set of the same thing, except that we
will call this time forError. Like this, passing him,
sorry, in Error parameter, that's it. We are good at our service. We continue with the creation of two components
which will be the first, which we create with ng-gc, It 's going to be Home, and the second one, which is going to be Home. And the second one will be SongCard,
that we will create inside Home. This will allow us to display the
small cards of each song. So we create that. Next, we will go to app.config. And here, we are going to activate the animations, since we are going
need it to animate the little Play button. Once we've done that, we surrender
in the app.root and we will render routable our Home component. Since it's the Home component, it's okay
be paf, empty, that is to say root. The component is called HomeComponent. A little comma here, like this. We should be able to have something
thing, there you go, HomeWorks, very good. We will open the HomeComponent.ts file. We will import the fontosome
module, the SongCard component. And that will be all for now. In terms of injections, we will have
need the SongService and we will have also need the ToastService. We will also need a variable
AllSongs which will contain all the songs. And this variable, we will use it to
iterate over it and display the small maps. So it's an ArrayTorridSong which
can obviously be undefined. For the moment, we will implement OnInit. Like that. Like this. And we're going to do a this.songService.getAll. We will also write a constructor
with effect inside, where we will recover the signal that is in the songService. This one, by the way, I have a small font here. We will recover the object that is inside,
which we will call allSongsResponse, a const. So if allSongsResponse contains OK. So, I just have to assign my list to
values that are in this object. Otherwise, I still test the Error status. I'm going to call the toastService to show my
user that there was a technical problem. An error occurred when fetching allSongs. And that's dangerous. Now we should be able to... We should make the request. So I can move on
of my HTML, like this. I erase everything in my
component, and I will create a first H1 tag, which will have a class, therefore a
margin-bottom 3 and margin-top 3 attribute. I will mark, so, goodMorning,
and I'm going to have a div with a flex and flex-vrape class. In this div, there will be... The forum, therefore, which will be
songOfAllSongsTrackByPublic. And this one is going to be our app
SongCard, which is going to have a class Card, SongCardBackground, MarginTop 0,
MarginBottom 4, MarginStart 0, MarginEnd 4. There you go, it will have as input Song,
I'm going to give him the song like this, and that's all for now. We're just going to close our tag like this,
and we are good for our HomeComponent. We need to add a little bit of style,
so we are going to go to the HomeComponent.scss, where we will first import our Bootstrap
Variable File, like this, with Assets, slash, scss, slash, Bootstrap Variable. We are going to add a class
Card, which will therefore have Flex. FlexBasis 14%, FlexGoro 0,
MaxWidth 200px, and CursorPointer. We will also have a series of
QueryMedia to manage and ensure let it be a little responsive. MaxWidth 1100px, for Card,
we have a FlexBasis of 21%. Then, we will have a HerbazMedia. From MinWidth to 1400px, and the same,
Card, the FlexBasis will be at 12%. Finally, last Query, we are going to have a
MinWidth of 1850px, with a class Card that has a FlexBasis of 14%. Very good, we can move on to
writing the SongCard part. So, we're going to clear a little bit here. We're going to open... The SongCard file. In terms of imports we will need
of font awesome module and here we go need a little animation. The animation will be to be able to animate
the little play as you can see here. We will try to keep the same behavior. So we will start with
write a trigger like this. We're going to do a little import. The name is InOutAnimation and
this trigger has a definition. So we have a transition. We're going to import that. The transition is The style will be transformed
transform translate y by 10 pixels like this with an opacity argument of 0, we close
the object and like this then we will make a animate which takes as parameter the timing which
is 2 0.2 seconds of type is out we go have in the style that is going to be transformed
and which will be of the same type as this one except that it will be 0 and here the opacity will change to
1 then we will have a second transition which is going to be the book release so I'm going
simply copy the book and this time here it is 0 to c10 and here it is is in and the opacity
it is reversed here we have our animation we will see how to use it right after
level our component will be able to implement init also we will create our input in mode
signal it will be input point required of type read song we're going to have to import all that a little
and we will also have to import input like this with small display lines since it will
we need to modify the state and for the moment It's not possible to do it with signals. We will make favorite false and
display play false at the moment. In the ngOnInit we will make a this.song
display equals this.song like this and we will create onOverPlay method to show icon
animation which will take as a parameter a boolean like this I forgot to put two
void points and one So that, we will have to assign so song.displayPlay equals displayIcon. And for now, that's all we need. We will now go to the HTML part. We therefore erase the content and
we will create a first div. This first div, it will have a
mouseOver which will call onOverPlay to true. We're going to have a second event that we're going to
try to listen which is going to be called onMouseLive. onOverPlay and which will pass false. In this div, there will obviously be a
another div which will have a cardBody class. In this div, we will have a tag
image which will have a class attribute with cardImg-top and cardImg-bottom. Then, src which will be the cover. So like this, we're going to make a
dataSongDisplay.coverContentType. plus base64 plus songDisplay.cover. This is because in fact, the image,
it comes to us in base64. So, we display it as such. And we're going to put alt cover. Like this. Then, we will have an H5 tag which
will contain classTitle and marginTop3. Inside, there will be the title of the song. So a title.value. Next, we will make a P tag. With an author attribute. And inside, we will mark the name of the author. .value. Like this. Finally, we are going to manage our
animation with an if song.value. Display.displayplay a div with a play class,
position absolute, d, flex, align, item center. We declare that it must be this div
which is the animation, so in-out animation. In this div, we will place the play icon with
a text class so that it is green and the name of the icon which will be circleplay. There it is, like this. I'm going to import this... Ah, it's already imported. So that's good. Now we need to do a little bit of CSS. I will import the assets file
bootstrap variable, like this. We will have title and the author class which
will have an ellipsis overflow text, whitespace novap and overflow hidden. We will also have a play class and
font-en-sum icon with a font-size of 45 pixels and then a play class which will
therefore contain bottom 105px, right 32px, border-radius 50px, background-color dark,
border-style none, width 35px and height 35px. Normally, we should be able to go to our
browser and we should have this display. So, there is still a small problem with
hover level since it is not present. It's quite simply because here we have to
adds a class to its card background with a cursor winter a background color a card inactive
background and then we will manage the over like this like this background color card over background
border radius card border radius that's how this we should have a display here's more
close and we have the good animation now we are going to move on to the bookstore part so bookstore
this is the part on the left where we are going to come list the songs to do this we will create a
reusable component therefore with ngc in The shared folder which we will call smallsongcard. As its name suggests, it will just
contain a small card with information, the small cover, the title and the author
the map, the music, sorry. Once we've done that, we'll come back to it. We go to component.ts library. Here, we will correct its module authors
and we will also import smallsongcard. Then, we implement onInit in this component. We inject our song service. We create a readSong array which is empty. In the ngOnInit, we will create a
fetchSong method which will be loaded to call our service, so getAll. Next, we will create a constructor with effect. We will wait for the getAll service to return. We're going to do if this.songService. The get all signal if its status is equal to
ok then we will assign the table that we created above the value found in the
signal.value like this now we can pass to the html part and in this part we had
already made the header we will add a div with des Flex, flex colon, rounded bottom 2, bookstore
hate, overflow scroll, bg dark, and padding 2. In this div, we will have our fort, which will
be song of songs, and track by song.publicid. In this fort, we are going to recreate a div
with classes margin top 1 and song card and padding library 1. Then, we're going to add our app here. Small song card so it has an input song and
that's all for now, so let's move on next in the small song card app we create our input input
of type read song and then we can directly go to your template where you delete what is there
we have already created a div with with a class flex and width 100 in this div we will put
an image tag with a rounded to class width of 55 pixels and a height of 55 pixels
a src which therefore has data plus content type with base 64 of our cover song.cover
like this then we will put another div with a class flex d point flex flex column
justify content center and margin start in this div we will put the first div which will
contain the title with a margin bottom class 1 the title class and then song pointei title
point value second div below this one will just contain author no need for margin
bottom 1 don't need margin bottom 1 to a text class below queen dot bottom
and we are going to have a motor directly like that. Here, we will have to
add a little bit of CSS. We will first import our variables, like this. We will have library 8, which
will contain 8 and library 8. Then, library songs,
with an overflow, Y, scroll. A library songs card, there is a cursor,
point, and a bookstore songs card, over, with a background, color, card, over, background,
and border, radius, card, border, radius. Let's take a look at what
That 's it, on the browser side. So, we have what we need, on the other hand,
There is a small defect in the height. It's because this class. Is reused in several places, therefore
you have to put it in the style.scss, color, it's secondary, and font size, font size small. There you go, we no longer have the effect that resembles Spotify. We will now move on to the player part and
we start with the back-end we will go to the song content repository and we will add a
method optional song content find one by song public id which takes a public uuid as parameter
id we now move on to our service we provides a service in the song and we are going to add a
public optional method which will send a song content dto and which will be called get one by public
id and which takes a uuid as a parameter we call our repository with the method we have just
just to create we pass the public as a parameter id we create a local variable of this optional
song by public id and then we're going to return a song by public id we will map with our song
mapper is the song content mapper song content song content dto we have our service we can
so move on to creating our endpoint. The endpoint will therefore be public. It will return a response
entity of song content DTO. It will be called getOneByPublicId. It will have as a parameter a request param which will
be of type UUID and which will be called publicId. We will denote it with an at sign getMapping. And the address of this endpoint,
It 's going to be songs.getContent. That's it, then we're going to call our song
service with the method we just created. Then, we create the locale
variable which is an optional. We're going to rename it, it's songContentByPublicId. And we will return a songContent.map
response entity OK. Otherwise... We are going to do an else get response entity. And we're going to return a problemDetail. We're going to go to the line to
let it be clearer. With an HTTP status .http. Bad request, with a detail
from, we will mark UOID unknown. So, yes, it's because
I forgot the build point. There you go, our endpoint is complete. Now we move on to implementation
from our player on the front side. First, we will add
a dependence that will allow us to play music in a browser. To do this, we do npm install
hauler, a JavaScript library which allows you to play music. We also add the types to have
autocompletion in JavaScript, hauler, and we make a .save dependency for them
add in dev dependencies. Once everything is done, we can
go to songmodel.ts where we go add a new interface. We will call this interface song-content. It will extend read-song and it will contain
a file of type string and a file-content. type, string type also, tent-type. Then, we move on to our service. So we're going to create one
new in our terminal. We do ng-gs and we will create
service slash song-content. Then, we open our service. We inject the HTTP client. And we will create a first signal which will be called
QToPlay, which is a WriteableSignal that goes contain a ReadSong stop and which will be equal
to a signal with an empty array inside. We create the public signal using Computed. We make a zis.QToPlay. Then, we will create a second signal which will
be called Play and which will be of type State, State of SongContent of HTTPErrorResponse. This one will be used to recover the
content of music before listening to it. We do State.Builder.Content.HttpErrorResponse. For init.Build. Then, our public signal, the
PlayNewSong, which we're going to call. It's Computed with Play inside. So, this signal will serve us, by
done, to maintain a queue and each music will pass through this signal. This signal will contain the queue of
Song and this signal will contain the current song. Now that we have this, we will
be able to create our signal. Method that will create this queue. We are going to create a new view with
first song parameter which is of type read song and in second parameter songs to play. It's a read song stop. It sends void. First we will extract the first
song from our song to play board. So we're going to make a song to play.filter
song and we will say song.publicid must be different from first song.publicid. Then we are going to do an if again, we are going to do
un???? its to play so we make a song to play at point a shift of first song. What it's going to do is it's going to
put the first song at the front of the table. Then we just have to do this.q to play .set
and we send the table in the signal. Afterwards following method we will do the method for
that every time that the unawakening either from the heart of wandering, changes
music, we will go and retrieve its contents. We'll call it fetchNextSong, song-to-play,
which is of type song-content, which returns void. We are going to create a query param, so
new http param.setPublicId, and the value is song-to-play.publicId. Then we're going to make another get call, so
environment.api.url, slash api slash songs slash get-content, and we add param query param. Then we can subscribe. Marking so we will manage the next case that will suit us
send back a happy song and in this next one we will make a this point map read song to song content
this method we are going to create it it will take into account song content and song to play parameters and then
we will just have to send the song content in the signal thanks to the state builder so this one
will be of type song content http error response for success and song content it is just necessary that
here I specify in get song content and in case error I made a this point play point set
and I'm going to do the same thing here what I I'm going to pass the error into parameters, now we're going to
write the signal using the state builder so this one we are going to write our map method here it is
our map method will just transfer the fields from one object to another therefore from its content
rather from song to play to song then here it is read song and we're going to do song to play point cover
song content point cover content type song to play point cover content type sound content point
title equal to song to play point Title son content point autor is equal to song to play point autor
his favorite content point is equal to song to play point favorite we are good for our service
now we move on to creating our player we go into our terminal and create our
component in layout slash player Once Once it's done, we go to app-component.html
and we will add it directly. We add a footer tag and in this
footer, we add app-player and we just add a width 100% class. That's it, now if we
comes back, we have player-works. Alright. We now go to player-component.ts. In terms of imports, we
will have the font-sum module. We will have the form module. We will have the small-song-card-component. And for now, that's it. We are going to inject the song-content-service. We're going to need a current-song variable
which will be of type song-content or undefined. And for the moment, it will be undefined. We're going to need an instance of
our player which will be current-all. We're going to import the import. We will do it manually. We're going to import all from aller. We will also need a boolean
is-playing which will be false for the moment. We will need the current-volume
which by default will be 0.5. We will also have a boolean is-muted. And then we're going to have two different tables. A first one which will be called next-queue. Who's going to be like... Of the stop-song-happy type and
which will be an empty array. And another array, previous-queue,
which is the same, a song-content stop and which is currently empty. These two tables will help us. To manage the before and after with the
forward and backward buttons. Then, at the level of our constructor,
we will have a first effect which will consume the queue. We're going to form a new line and who's going to listen
a song content service.queue to play. We check if the new queue has
a length greater than zero. And if that's the case, we're going to
call an onNewQueue method. We're going to create right away. Like this, we will be able to create our
queue in this method. In this method, we will record in
this class the state of the new queue and we are going to call a method which will be called
this.playNextSongInQueue since when we created a new queue, we
wants to play the first song in this queue. We add void, void. This method will have an if this.nextQueue.length
is greater than 0 then we pass the boolean this.playing to false, if this.currentSong. So we're going to queue
previous, first we will put the song current, then we will take the song
next, next queue, and therefore from the next array tail and we're going to send it to the happy song
service so that it can be retrieved and listened to. That's it. This effect. Then we will have a second effect
who will listen there when there is a new song to play. So we're going to do an if
this.songContentService.playNewSong.statue="ok". There, we will directly call this.onNextSong. We create this method and this
method, it will extract the song, the current signal song. And we will create a new instance of the player. So new all instance, gala new all. In terms of options, we will
have SRC, which is an array. And there, we are going to give it data, we
will give him the music data. And so it will be
this.currentSong.fileContentType, base suite 64, this.currentSong.file, and that's it. Then we will have a volume. There, we just have to pass the variable to it. CurrentVolume, we will have a method
onPlay, we will call our own method, we will create it right after, onPlay, we
will have onPause, this.onPause, we will have onEnd, this.onEnd, and that's it
for configuring this instance. Then, if an instance of all is already
present, then we stop it, the news instance, we play, and we replace the
current instance, the old one, by the new one. Next, we will create our onPlay method, which
will set the boolean isPlaying to true, we will write onPause, which will set isPlaying to false,
and onEnd, which will call playNextSong in queue, and which will set the isPlaying boolean to false. Next, we will also manage the buttons. OnForward, Backward, so we're going to create a method. OnForward, which will call playNextSong
in queue, and onBackward, which will do a if if there is something in the queue. So, we're going to set isPlaying to false. We'll see if there's a song
common, and if this is the case, we will put first in the next queue. And we're going to say... " Unstack the previous one". Song in queue
previous.shift Now that we've extracted it, we will send it to the fetch next song service
previous song Another method, we're going to have a break. If there is an instance of all, then we will
simply call the pause method. And for play, if there is an instance
of all, then we call play. Regarding the method
mute, we will return void too. If he exists... An instance of all so we will call this
point is muted we are going to invert it we are going to invert the boolean we will call mute on the instance of
all and if muted is muted is equal to true then the current volume must be equal to zero otherwise the
current volume must go to 0.5 and we must go this volume level at all current this current
volume then last method we will have the all volume changes which will take a parameter a new
volume of type number which sends void and which goes so divide current volume which will divide rather
new volume by 100 because we will see that more late it is because of the range therefore of the input if we
has an instance of all then the volume must be from this point current volume if the current volume
is equal to zero then is muted is equal to true and the player instance of all must go to mute
otherwise if one is already mutated then mute must be equal to false and we must remove the mute from the player
that's it for the player's part, let's move on now in the template part we open the html we create
a first div which will be which will contain therefore flex justify content between align item
center player and I have a Then, inside from this div, we will have another div with a
class song played des flex align item center. Here we will display the music
current, so if current song. So, we're going to call our small song card. We will pass it as a parameter
song and the current song. We'll see what it looks like here. So here, obviously, there is nothing displayed
since we don't yet have a song loaded. We add another div here with the class of
flex justify content center and align item center. We add an icon here which
will have a control class. The name of the icon will be backward step and
we will call the backward method when clicking. If the player is playing a
music, then we display the pause icon which will have an MX4 play icon class. When clicked, we call pause and the
name of the icon is circle pause. If this is not the case, we will copy this line. We call play and the name of
the icon is circle play. Finally, we get this back. This one is forward and we click. It's we forward then we will have a div
with a class of flex justify content between align item center and volume in this div we go
have another div which will be called volume-icon with the click we will call we mute if is muted
or the current volume is equal to zero then we will display a mute volume icon otherwise if the
current volume is smaller than 0.3 then we will display volume low otherwise if current volume is
greater than or equal to 1 then we will display volume high finally we will have here a range type input
with a class form range margin start 2 which goes have an input of type range with a form class
range margin start 2 which will have an input of type range margin start 2 which will have an input
of type range margin start 2 which will have an input of type volume Change with $event inside
now if we go on that then it we must actually have some css missing and it
you also have to import the icons so we have fa backward step fa forward step then we have
circle play circle play already present circle pause volume mute volume low volume high and I think
that's enough, then we'll move on in css the css we will import the assets sheet
like this we will have a player class with a margin left of 14px a margin right of 12px a
margin bottom of 9px a height of footer height inside we will have a check with a
font size of 8px and a margin bottom of 9px and a margin bottom of 8px and a margin bottom of
25px a secondary color we will then have control but the over version which will be white on
will have a play icon class with a font size of 33px and a white color and a cursor pointer
we will also have the play icon but the version over which will do a transform scale 1.09 we will
have a play icon class with a range form with a width of 130px volume which will have a
Width of 130px volume icon who is going to have a child fa icon with a width of 30px and a font size
of 17px we also have a range shape with a width of 100px finally we will have a range shape
with a song played with a width of 250px. There you go, now everything is a little more in place. We can see that there, it's pretty good. There you go, that too. So our player is OK. Now you will have to plug it in and test it. To do this, we will
render in the home component. In the home component, you can
get rid of onInit now. We can also get rid of song.getAll,
since it is the bookstore that takes care of it. Now, regarding our player, we will
have to inject the songContentService like this. And we will create an onPlaySong method which will
take songToPlayFirst as a parameter, which will be of type readSong, which will therefore call
songContentService.createNewQueue with... with a songToPlayFirst. And then we send all the
song chart that we recovered. Then, on the template side. So, we're just going to clear the air a little bit here. On the template side, at the songCard level,
we will add a songToPlay here which will be equal to onPlaySong$event. We will create this output in the songCard. And in the songCard, we will have
a playSong method, which will call songToPlay.next.this.songDisplay. And so, this method, we will
call it from the songCard side. Here, we're going to call it on click. Then, we will do the same thing on the bookstore side. So, first of all, we will
go to the smallSongCard. And we're going to go to the smallSongCard. And we're going to add an output to it
as we did for the SoundCard. We will call SongToPlay in the same way. This is a ReadSong type transmitter event. We will note it with the base Output. And we will add a Play method which
will make a Next thanks to this.song. That's it, this way. Now we can return to Library. In Library, we will inject our
SongContent, our SongContentService. We will create an OnPlaySong method which will take
in parameter SongToPlayFirst which sends Void. And in there, we're going to call the
SongContentService.createNewQueue, SongToPlayFirst. And we send the whole painting of Song. Then, we go to the template. And we're going to link. Both. So it's SongToPlay and
this one is OnPlaySong. That's it, so now, if I click on
example on this music, it doesn't work. So yes, we forgot to
put here click Play. There you go, and there it works. So here we have a little problem of
height, and this is due to the fact that the icons are not the correct size. So we're going to have to go to the
style.scss, and add a small class that we have forgotten, which is called
navicon, and which must be font-size 20px. Once we've done that, the height is correct. Then, the goal will be to prevent
users who are not logged in be able to read music, and display them
a pop-up inviting them to log in. To do this, we are going to go to the
off-service, we will create a type, export type of pop-up state, is equal to open or close. Then, we will create a new signal, that's fine
be trigger of pop-up dollar, which will be of type writable signal, with the type that we just... create, and its initial state will be closed
since the pop-up will need to be closed. We create our public signal, state change,
with computed, so this.trigger of pop-up, and then, we will create a service that we
will call open or close of pop-up, which will take a state of type as a parameter
off pop-up, and that will simply write in the signal, the new state. Now that we've done that, we're going to create the
component where we will display our two buttons which will invite users to log in. To do this, open a terminal
and we do ng-gc, we will create it in layout and we're going to call it off pop-up. Once created we open the ts part and we go
inject the of service like this then we will just create a login method we will call the of
service point login once that's done we will move on to the html part where we will add a
div with a bg dark class of flex align items center flex columns padding 4 and of pop-up in
this div we will have an h2 tag with a class margin bottom 4 we will write start listening now
with a free account below we will have a another div with a class hate 75 a width of 100
a class default flex flex columns and justify content center we will have a first button
who will call who will call the login sorry we click a btn class primary button one
width of 100 and a margin bottom of 3 it is type button it will be called login we will do
even for the second button which also works call login except that we will call it sign up on
will display sign up then we will go to the scss file to add a small class
which will be called of pop-up and which will contain a Hate by 250 pixels then we will go to
the app component because it is this component which will manage the opening and closing of the pop-up
to do this we will add the of service we will also inject the modal service which comes from
bootstrap And we're going to need an instance of the pop-up, which is of the NGB modal type, and which
can be zero, and which is zero elsewhere. Then, we're going to go into the constructor, we're going to
add effecte, In effect we will call a method we will create just then so it will be
open or close modal and we will pass the value to it which is found in the signal which contains the state
of our pop up will also have to add an option who is going to be in the water signals rights here we go
create our method it is the type of pop up state in our method we will also have the state
so we're going to call it state Is equal to open then we open the pop-up this is the method we go there
create just after otherwise if we have an instance of the pop-up and if the state is equal to close
and if there is not and if there is a A modal which is open it's modal service so we close
the modal then for the open of pop up method we will therefore instantiate a new pop up using
to the service modal we will pass the component to it that we created previously with a few options
authentication pull model and we will center it like this then we will have to listen to some
events so we are going to do an off modal ref point dismiss when the user will click
on the gold edge of the pop up it will close automatically we have to be aware of that
so in next we will recall the of service point open or close and we will give him the order
to close so that the state is synchronized with opening the pop up does the same thing
here with closed and we are ok for the part opening closing now we will need
a way to detect when the user is not connected and when he tries to play music
so to do this we will create an interceptor which will come listen to http requests and see what
the back-end responds to us and based on that we will react to Create an interceptor on type ngg
interceptor and we're going to do it here we're going to do it here we're going to do it here we're going to call it so off
interceptor and we will put it in the folder service like this when we open the interceptor
we are going to put we are going to inject the Off service like this then we will give a blowjob on request
we will call tap and in tap we will see if it there is an error the error will be of type http
error response and if the error it has a status equal to 401 and if the error has a url and if this
url it includes api slash authentic quest and id user and it is authenticated on the front side then
we try to relog it automatically otherwise if the error at a url and that request point method
is different from get and it does not terminate not include non and with pardon with api slash
song what we want, she can list them or that always the same if it has an url and if
this url does not end with api slash quest authentic and id and if the user is not
connected then in these cases we open the pop up open or close open here is our interceptor
now we will have to activate it that is to say register it here so at the level of provide Http
client we are going to call with interceptors it is a table that we will have to import it and in
this table we just want marked off interceptor Besides, it's not very well named because
that it makes interceptor interceptor so we are going refactor it yes that's it and we also want to do
a refactor of this file like this normally now if I return to the side of
my browser that I click I have my pop up when i try to read against i think there is
a small color problem is not a good barrel primarie but it is in the secondary barrel in
off pop up component html here it is like this and so if I log in there I have the player
which works small problem which I just realized account by recreating my database because
that some music was poorly created in my case when you add a music to the list
is not refreshed correctly after creation this is normal since if we go to add song
component we had here commented on the get all I prompt to uncomment it and normally now
if I'm going to recreate any one so little no matter, here it refreshes correctly We will now continue
with the research part. So, we move on to our back-end. We're going to go to the song repository. We are going to add a method which will
return a list of songs that will be called findTitleOrAuthorContaining and which will take
as parameter a searchTerm string. This method will be noted with
arrobase. Query, because we are going to have need to do a custom query. This query will contain a select, s
from song, s, where lower de s.title, like, lower, concat, percentage, comma, two
points, search, term, comma, percentage, more, to the line, or, lower, for the field. We will do the same for the author field,
select, lower, from concat, we will copy that 's it, it will go faster, that's it, and it's
search, term, otherwise it won't work. So here we are going to try to match the search term
with either the title or the author once we did that we go to the song service we go
create a new public list method that will return a list of read song info dto this
alpha method was called search and it will take in parameter a search term we will call our
song repository with the method we created just before we will pass the search term as a parameter
we're going to make a stream map we're going to call the song maper with song to read we will transform the
entities in dto and we will collect all that in a list like this we will create the locale no
directly do return like this and we are good at the level of our service we move on to the part
resources we go to song resources we create a new endpoint which will send an entity response
playlist song info dto who will be called search and in parameters we will have a request
params which will be of type string and which will be called term this method it will be annotated
with a get mapping and its address it will be slash songs slash search inside Method
we are going to return an entity point response ok and we will call song service point search
by passing it the term parameter we are good we will test that our back end is working correctly
here we have recharged everything seems to be good we will now head towards the front end
to implement our research to do this we will first open the song service And
we will add a search method which will take as a parameter a newSearchTerm which will be a
string and which will return a state observable, stop, readSong and httpErrorResponse. We will create a queryParam variable which will
be equal to newHttpParam.set Our param it will be called term and the variable is
the parameter of the newSearchTerm method. Then we will call our httpClient,
we are going to do a get, this get it will be of readSong stop type. The url will be environment.api url
slash api slash songs slash search. We will pass the queryParam as an option. Then we will call the pipe method inside
with a map operator which will map the songs, the table, in state.builder of type shutdown
from readSong.httpErrorResponse.forSuccess. We're going to play him the songs and
we are going to do a build point. If there is an error, we will use operator 4. And we're going to send an observable
thanks to the off method. And we're going to do the same thing as here except
that we will return forError like this. We will put a small comma
and we will import off of RxJS. Once we have our service, we will
create our new search component thanks to ng, thanks to the Angular CLI. We create it directly at the root. There it should appear here. We will go to app.root.ts. We will add a pass, search,
with a component attribute. Search component, like this. We're going to go here, we're going to check that
when we click, we have search work. Next, we go to the ts file. We will import the form module, the fontosum
module and the small songcard component. We're going to need a search term
of type string, initialized to empty. We're going to need the song service,
which we will inject like this. We're going to need the song content service,
which we will inject in the same way. We're going to need toast service. We are going to create a table of songs to welcome
the search results, which will be of type stop read song, and which will be initialized. And we will also create a boolean which will give us
allow the user to wait until the request comes back from the server. We will create a private method which
will be called resetResultIfAntiTerm with newSearchTerm as parameter. We will use this method a little later. It will allow us to reset
the empty array if the length of the search term is zero. So it's a little easier. If newSearchTerm.length is zero, then
the resulting array of songs should be empty. We will also create an onPlay method. So he will... Because in fact, in research,
the user can also click on music and music must be played. So, it is also necessary... That in the same way as what we
did in the bookstore and in home, we can create a queue. So we do a create queue, first song,
and we then send the table of song results. Then, we will do our method
main, which will be the onSearch method. The onSearch method, it will take into account
newSearchTerm parameter, which is of type string. We will assign this newSearchTerm to searchTerm,
and then, we will transform this newSearchTerm into an observable, which we will import like this. We're going to do a blowjob. In this pipe, we are going to make a tap operator, which
will send us the newSearchTerm this way. We're going to call the reset function, like this. We will call the filter operator,
with newSearchTerm, like this. And we will filter the requests, if the
newSearchTerm.length, it is not greater than 0. Little comma, we're also going to do a debounce,
to avoid putting too much strain on the server, and to send him all requests each time
time the user types a letter. We will set an interval of 300 milliseconds. We are going to make a tap, which will allow us,
here, to be able to pass our search boolean with hole, To display the little spinner. We're going to make a switch map, then,
where we will call our service, like this, so songService.search. We will pass it a newSearchTerm,
and we will pass it a newSearchTerm, And finally, we can subscribe. In our subscription, we have just the case
next to manage, since the observable us returns the state alone with error or not,
so we will manage it in the onNext directly. And we will create an onNext method which
takes the searchState as a parameter. Like this, we will put a small
capital letter, like this, no period, no semicolon in this case. We do a createMethod, we create that. There, here, we import the correct state. Then, in onNext, we will pass
the isSearching boolean is false. Then, we will see if the state,
the status of the state is equal to OK. If this is the case, we will assign the table to
the value that is in the state, on the board. Otherwise, if there is an error, we will
display a toast with anErrorCuredWhile, searching of danger type. And that's it for the next part. Typescript from search now let's move on to
the html part we remove what is in this file then we will open a div with a
class absolute position input group flex no wrap search bar and zindex this div it will actually contain
an The input where the user will be able to type their search so we are going to make a span class border
0 rounded start 5 input-group-text and padding end 1 in this span there will be an icon icon
it's called search we're going to import it all from so as not to forget that's all
actually then we have an input which contains a ngmodel the ngmodel the variable is search term
and we will do an ngmodel change and we will call onsearch with the $event like this the name of
the input is search the class the classes sorry border 0 rounded end 5 shape control margin end
2 search-input and padding start 2 this input it goes to be of type search we will put a placeholder
only a small space what do you want to listen to and we're going to put an aria label search that's how
this then we will have a div with a class padding top in this div we are going to have a first
if if search is equal to true then I will have a div with a class padding top in this div
we will have a first if if search is equal to true then I will have A god with a class
flex justify content center margine top 5 and 100 then I will have another div with a
class spinner big text primarie the smell and in this god will see a span with a visual class y
hidden with in this political span then I see she if the stern sort we are going to paste just this
the stern kind does not feel comfortable it is superior to Or equal to 1, and if the song table results,
it is equal, we will have a div with a flex, a justify content center with a header of 100. There, like this, and inside, we go
have another div with marked no results for this search, sad emoji. Finally, last case, we will have an at sign
else, and in this one, we will have our for, with a song of songs result track by song. public id. In this for, we will have a div. Who is going to have a "Song Background" class, a
" Width" of 100, a "Margin Bottom" of 2, a "Padding" of 2, a "Padding End" of 3,
a "Flex" class and an "Align Item Center Then, in this "Div", we will have our
" Small Song Card", like this, where we're going to pass it to him in parameter our "Song". Finally, we are going to do
a small passage in the "SCSS" file. We will add a "Search Bar" class, where we will
do a "Transform", "Translate", "100px " minus "72px", "Width" 350px, "Height" 50px. And we will also add a size
for our "Phantosome Icon A font size of 21 pixels is therefore normally
we should be able to return here we are fine our field and if we try to look we see
that if I put letters as I go the search works correctly we have our
results and gradually we have the music which appears small oversight here because
that in fact the music doesn't play when you click so you must not forget to
add the listener like this with a play event here it is and when we search we must see here
the pop up showing that it triggers well and if I me log should be able to search and what music
fires correctly like this then we will also take the opportunity to add a small
loading icon in the bookstore and in the home to do this we are going to do a little bit of
empty here then we're going to go to the bookstore component we are going to add a boolean is
loading equals false in constructor in effect we will also add a boolean we
will change it to false and in fetch song we will set it to true, we're going to comment it out for
moment just to see clearly Let it appear well We now go to the HTML part and we
will add an if isLoading to this div. Here, I will already take the opportunity to put the else
like this and we will create a div with a class flex, flex colon rounded in volume 2, justify
content center, align item center and hate 100. In this div, we will put the icon, the spinner
grow, primary text, loader and role status. Then, a balispan, class,
visual hidden, loading. That's it, now if we return from
next to our bookstore, we see here the icon which is in loading mode. We will uncomment here and there
now, there it is, very good. We will now go to home. Home, component and we're going to do the same thing. We create a boolean isLoading set to false. In the constructor, we will pass it
to true and in the OK, rather in the effect, here we are going to set it to false. There it is, like this. Now, in the template, we will add
an if isLoading, the else like this. Here, we will come and copy directly
the spinner since it's the same. And no. Normally, we should have no effect, we
just going to remove this one, there you go, so it's a a little bit low, we're going to remove the haste without it, that's it. We now move on to managing favorites
so for this functionality we will add here as it is done in spotify kind of small
tiles where we are going so it is no longer displayed but a kind of small tiles which will group together
List of songs that the user has favorited we are also going to add hearts so at the level of
player on the right in the search also when we will do a search here for that we will pass
on the side of the tank we will first create small methods a utility method in the user
service this method it will be public and it will return an optional read user dto
it will be called get by email and will take into account parameter an email we call the user repository
made a fine one by email then we create the variable and we will map this result with a
simple user maper simplified very well then we're going to go to the other song repository we're going to
need two methods one method first who will send an optional of songs we will this
method we will call it find one by public heidi and learn in parameter a public helped the
second it will return a list of songs and it will be called find all favorites by user e ? ??
and it will take a list of songs as a parameter 15uuu'ue v� d'� from the public and she will take in
parameter An email this method we will note it in low query and because we are going to need to do
a join and we will not be able to do just in naming with spring data jpa this query it goes
do a select s from song s join with the table favorite f on s point public said equal f point
song public said where of user email is equal to the email that you pass in Settings. Once our repository and methods
are complete, we will import a StateBuilder. It is the same class that we find
at the front, but on the Java side. I invite you to retrieve it from the description. For my part, I will import it into
infrastructure by creating a new package since this state is to be shared. In this way, add and I also
need status notification. Then I go to SongService and
I create my method which will allow me to add and remove my favorites. This method will return a state of
favoriteSongDTO and the error will be of type string. We'll call it addOrRemoveFromFavorite. It will take a favoriteSongDTO as a parameter
and the email of the user who liked it. We will instantiate our builder like this. We're just going to change the type here
which is going to be string favoriteSongDTO. Alright. We are going to call our songRepository in
doing a findOneByPublicId and we will fetch this public ID in the DTO. We will now create our local variable. We will call songToLikeOptional. We'll look if we can't find it. So, we return an error
directly in the builder. SongPublicIdDoesn'tExist.build
Otherwise, we will extract. There, songToLike is optional. We're going to load the user. So there, the userService, it
We'll have to inject it. So, private final userService. We will add it here as a constructor,
as constructor parameter from songService, Let us have the injection. We return to our userService and
we do a getByEmail or else throw. Since here, anyway,
the user is logged in. If he has favorited, then we will add
an entry into the table, favorite, so we creates the entity, and there is the new favorite,
favorite.set, the public ID, we extract it from song to like, then we add the email,
I can name this one a little better, I can put user or like song.email. And then I'm going to come and save
basically my favorite object. I need the favorite repository, which I
will also inject into the constructor. And if he wants to remove this song from his
favorites, then we will create a favorite ID thanks to song to like and to the user, and we will come
doing a delete using the foreign key is delete by ID, thanks to the composite primary key. Then, we will send back, a song dto, I have
saw a song dto, false, with its public ID. Finally, we are going to return to the builder, we are going to
return the builder, port success, and we will return the song dto, making a .build. We can continue with the service
which will allow us to recover favorite songs for a user. This method will return a
list, From ReadSongInfo.dto, we will call it FetchFavoriteSongs and it
will take an email as a parameter. We're going to call the SongRepository, we're going to do
a FindAllFavoriteByUserEmail, we pass the email as a parameter, we make a stream,
then a map, we will call the SongMapper, we will call the SongToReadSongInfo.dto
and then we're going to call List. That 's it, we'll return it directly
and we have our service. We can now move towards
the endpoint, therefore towards the SongResource, where we will create two new endpoints. The first endpoint we go to
create, it will be the one that allows Add and remove favorites. So it will return a
ResponseEntity from FavoriteSongs.dto. The method will be called AddOrRemoveFrom. We are going to need a bin which is
valid, which is contained in the body and which is of type FavoriteSongs.dto. We first go, in the UserService,
retrieve the user who is in session, like this, UserFromAuthentication. We will then call the service, AddOrRemove. We'll give him the DTO and the email. From the user we will create our variable if
then we will look at the state level status equals status notification point
error then we will create a problem style point strong status we will send http status point bad
request and we will send the details of the error by doing like this we will then return
the entity response with the detail problem alters and we will do a build point if everything
went well then we send back a response entity with status code ok and with content
of the state that I can name it a little bit best favorite song response we don't forget
here annotated note endpoints with post mapping the address will be slash songs slash like on
now moves to our second endpoint which will send a response list entities
readsong info dto it will be called fetch favorite songs we are going to come here to read the information of
the user connected as we did more high and we will come and make a response response
entity point of songservice.fetchfavoritesong by passing the email as a parameter. Finally we annotate with getmapping and
the address is songs slash like. Then we will rebuild our tank and
we check that everything is going well. To give you more details on how
it works on the database side at the level of the structure, if we take a Our pgadmin, you
can see that we have a table favorite song. If we look, it has two columns
which are userEmail and songPublicId. And in fact, this table, if there is an entry,
we consider that the user has favorited it. And so, it is for this reason that in
the songRepository, we make a join to come map a song with a line in
the favorite table based on userEmail. Finally, now, on the side of our service,
we will have to add a method which will come load the information of the songs that are
favorites for users who are logged in. So, we're going to go to our songService and
we are going to add a method which will be private, which will return a list of readSongInfoDTO. We're going to call fetchFavorite. statusForSongs, which will take as a parameter a
list of readSongInfoDTO, we will call song. In this method, we will recover the
information of the logged in user, we will call authenticatedUser. Then, we will retrieve the list T uuid t song
by making a song.stream.map read song info dto get public id we will transform it into a list
like this it's song public id then we're going call our favorite repository we are going to make a
find all by user email and song public id in �a on we will have to create it and we will go to parameters
authenticated user email and song public id on will go to the favorite repository this
method it will return a list of favorites and so it's find all user email and song public
id here it is like this then here we will create a local variable this is going to be the user favorite
songs and we will return songs.stream.pick song and if user favorite songs contains songs
get public id then this song is in favorites and we will we will return to list and
So this guy here, we need to make a stream.map and make a favorite of getSongPublicId. This list must also be transformed. Like this, like that, there it works. Then we're going to have to go to our
getAll and then in our search. So in our getAll, we will have to
transform our service a little. Here, we will extract the variable,
we're going to mark allSongs. We're going to return allSongs. On the other hand, we are going to do an if on the userService. If the user, he is
authenticated, then we will return a fetchFavoriteSong with the following array. There it is, fetchFavoriteStatus. We will create the method
isAuthenticated in the userService. And we're going to return securityContextHolder.getSong. getContext.getAuthentication.getPrincipal.equals.anonymousUser. There you have it, anonymousUser. Then we go to the search method
and we're going to have to modify it too. So we're going to extract here
variable, so searchSong, the list. And we're going to do the same thing. If the user is logged in, then
we return the favorites information. We will now move on to the front-end side to
implement our favorites functionality. We will first go to the song service. Here, we will implement the service call of
our endpoint that we have just created. For this, we add a method
addOrRemoveAsFavorite with a parameter boolean and a public ID of type string. This method returns void. We are going to call our HTTP client in
making a readSong type post. The address is environment.api underscore
url slash api slash song slash like. The bodysuit will be favorite and public ID. On this, we are going to do... We're going to subscribe. We will manage the next case which will be updatedSong. Here, I will have to create the two signals. So the private signal which goes
be called addOrRemoveFavoriteSong which will be of type writableSignal. From readSong array state. It's not an array, it's just an
readSong and HTTP error response which will be equal to signal state.builder of type
readSong HTTP error response and for init.build. Another signal here that's going to be equal
to computed 6.addOrRemove like this. We return to the side of our service. Here, we are going to do an addOrRemove
at the signal level. We're going to do a set and we're going to do a
state.builder of type readSong HTTP error response .forSuccess.build
with the readSong object inside. We will also check if the updatedSong,
the favorite field is true and if it is case, we will call the toastService.show and
we will notify the user that the song has been successfully added to her favorites. It is a success. I will inject toastService like this. And if this boolean is false, then I will
do a toService.show and in these cases, I will notify him that he has removed it. From favorite. No, it's still a success. This is not the case of error. In the event of an error, we will simply notify the
signal by sending a state in error, by making forError like this and passing through putError. Then, we're going to use the toast again in mode
danger and this time, we will mark as Error Error when adding song to favorite. So much for our service. Then we're going to need a second serve
which will allow us to recover the favorites. To do this, it will send void
and we will call the HTTP client. In get mode we will recover a read stop
song the url is environment.api url slash api slash songs slash like we subscribe we manage it
case next with the table of favorite song which returned to us by the API here I have to
create my two signals my fetch favorite song I will add here it is stopped reading
song the same thing here it's for init here it's this signal and it is this signal like this sig I
will also add sig here now we can return to our service we are going to do a set
in this signal which will be of state point type type builder stop read song http error
point for success favorite song point build in this which concerns the case of error we will make a mistake
and we will just return the builder with an error like this now we are going to build a component
reusable which will make it possible to manage the favorites to do this we will generate a new
component which will be called favorite song button he's going to be in shared now we're
head into the shared favorite song button and let's find ourselves in shared now we're heading
in shared On import font to sum module on will create an input which is required which is
type read song we will have to inject off service And songService. We are going to add an onFavorite method,
which will be called when the user will click on the heart icon. We will pass a readSong as a parameter,
and in this case, we will call the songService, with the service we created,
and we will reverse song.favorite, and we will also send the publicId, like this. Next, we need a constructor
in which we will place our effect. We will come and collect the
signal, therefore favoriteSongState. If this state is equal to OK, and the value... Is equal to true and the song its public id is
equals the public id found in the song favorite then the favorite song point is equal
at value point favorite in these cases we can assign the value we can now move on
template we erase the content we put an if which will see if the user is logged in and
if this is the case we will display the favorite button we will first listen to the click and we will call
we favorite by passing in song parameters if the the music is in favorite so we will display a
icon with a full heart we will call favorite text primari the icon here we will make a table
with a face key and Art like this and then we are going to have an else except this time flagship
so it's basically regular and this one I don't remember the exact name but it's
so that you may have a full heart and an empty heart. Then we will extend it scss and we
will just add the favorite class with a font size of 20 pixels. Our component is finished, we have nothing left
that to place it we go to the search in the template and we will add our component here
so favorite song button and we will pass it on in parameters the song like this and we will do
the same thing for the player component where we go place it like this, here it is current song. Now we can return to our side
front we have to log in there and if I search here, ah yes then small problem
of icon we have to import so it's done art here and then there is a little import
a little special to do here I do import art art regular from except that this one is
the regular here and this one I have to It matters because they have the same names. We are obliged to put an alias at the level
of our imports I must be able to come back here So look for a little css problem we're going to
now create the map that will allow us to access the list of music that we have put
as a favorite for this we will create a new component which will be called favorite song
card we will create this component in home. Once in Home, we will open the TypeScript,
we will just import RouterLink and we can then go directly to the HTML. This HTML will have a div with a class
who's going to be called Card, we're going to have a SongCardBackground and a Playlist-Card. In this div, we will have another div with
a CardBody class, with padding 1, a T-Flex class, a width of 100 and rounded 2. We will also have a RouterLink which
will take us to the favorite URL. Then, we will have an image which will
have a class rounded 2, a width of 55 pixels, a height of 55 pixels, a species. A SRC which will be equal to, then I have
forgot to close the quotes here, an SRC which will be equal to Assets.img. Slash like.png we will put alt it will be like
song like this, this image you will be able to find it in the repository, I will
import it into a folder that I will create, which is going to be called img and I'm going to put it here,
then we will have a div with a class d flex flex colon justify content center with a
margin start of 2, then in this div, we will put another div with a margin bottom 1
and 1, a title class, this div will contain like song, then we're going to go to the scss and we have
just a class to add which will be playlist card which will have a width of 200 pixels and
cursor point very well, now we will place this map at home level, so we are going to
render in the home component template on will add an h1 title which will have a class
margin bottom 3 inside we are going to mark playlist and we will add our favorite app
song card like this, if we go back now in our home, here we have our little one
tile like song, then we move on to the creation of the component Which will contain the list of components,
it will be called favorite We go to the app-route to add a URL to it. Her pass will be favorite, and therefore
the component is favorite-component. We go to the favorite TypeScript file. We will import favorite-song-bouton-component
and small-song-card. We will implement onInit, like this. We are going to create a favorite-song table,
which will be of the read-song stop type, which will be empty for the moment. We are going to inject the song-service. Like this, we will also have
need the song-content-service. In the ng-onInit, we will call
the song-service.fetch-favorite. Here there is a small size for favorite-songs. We will then create our component. Builder with effect inside. We will extract the signal from song-service,
which will allow us to control when the user removes songs from favorites. Like this, we will test the
status, if it is equal to OK. And if that's the case, we're going to
refresh the favorites list. Then we will have a second effect
who will take care of retrieving the list. So we add our effect like this. We will recover in his service
the FetchFavoriteSongSignal. We create our FavoriteSongState variable. If the status of this variable
is OK, then we extract the value. Above, we are going to do a forEach. And in this forEach, we go
skip all the songs with holes. Finally, we will assign... This board on the board that is in the State. Finally, if the user clicks on a
music, we have to be able to play it. So we'll have to call
service that is present in SongContent. Like this, SongContent.createNewCue
FirstSong.This.FavoriteSongs On can now move on to the HTML side. We will put an H1 tag with a class
MarginBottom3 which contains LikeSongs. A div with a PaddingTop class. A for which will iterate over the table. FavoriteSongs.TrackSong.PublicId In the for,
we have a div with a SongCardBackground class. Width 100.MarginBottom2. Padding2.PaddingEnd3. Tflex. AlignItemCenter In there, we will have our
SmallSongCard app which will have a class of Width 100 to which we will pass in Song parameter. Like this. When clicked, we will call OnPlay. We're going to play the song like this. And finally, we're going to do AppFavorite. It's the button. Like this. With Song. There we have our Favorite. So, we have a problem. We have a BadRequest problem. If we go to see in Network, it's
because in fact, we don't send. There is a mistake. Here we send Favorite. We do not send the Boolean. So, indeed, there is a typography. Here you have to put Favorite and PublicId. Let's see if that fixes the problem. So. There, we are good. I can put it in Favorite. If I click here, it's also in Favorite. And I can also take it off. We can clearly see that... It works here small error so it works when
same but it's less elegant we don't click but we listen to the song to play which can be found here
now we can return to our side browser we return to like song and we can
see that it works correctly that we can put for example here as a favorite we can do like song
we can remove some and everything works correctly. can also remove the test toast that we had
left until now in the app component in the onimit vng our spotify clone is
now functional we can play music manage queue control volume
search for music put it in favorites manage our favorites and add music thank you
for having followed this tutorial to the end I hope that you liked it I'll tell you next time ciao