#Springit
This is a reddit clone project using spring boot 2.5.5
automated restart set preference -> build... -> compiler -> auto build preference -> advanced -> auto-make
configuration resources/application.properties change configuration like auto restart/port java/*/config/SpringitProperties.java eg. server.port=8085/ spring.devtools.restart.enabled=false lsof -i:8080 check which process is occupying 8080 port
configuration processor org.springframework.boot spring-boot-configuration-processor true generate meta-data under target/classes/META_INF/*.jason
- Let some operations happen only in certain profile Profile(String) in application.java set spring.profiles.active = [any String] in application.properties
2.configure different environment using profiles touch a new applicaton-[profile].properties under resourses/ and set set spring.profiles.active = [any String] in application.properties
can use evaluate to check the API ApplicationContext
- set logging level: set logging.level.root=TRACE in application.properties for all classes
- logging for a specific class import org.slf4j.Logger import org.slf4j.LoggerFactory Logger log = LoggerFactory.getLoggers(SpringitApplication.class) log.error(String) set logging.level.com.lunz.springit=DEBUG
add some info of app info.application.(name/description/version) management.endpoint.health.show-details=always management.endpoints.web.exposure.include=health,info (expose the endpoints) all in application.properties
Spring data is a large project containing tons of modules like support for JDBC, JPA, MongoDB/Redis JPA is just the specification and by itself isn’t all that usefull. What you need is a JPA provider that implements the specification like Hibernate
Must use annotations to let Spring know the class we created are entities, use @Entity @ID annotation specifies the primary key of the entity @GeneratedValue annotation provides strategies for the generation of primary key values
Refactor, right click package, select refactor and rename, it will automatically update the name of this package in all files Needed for Lombok Project: turn on annotation processor - preference/compiler/annotation processors/enable annotation processing @RequiredArgsConstructor @Getter @Setter @NoArgsConstructor above to replace constructor, getter and setter @NonNull for a certain property: this is required, automatically create a constructor that instantiates this object
after creating the domain objects, need a mechanism to get data in and out of our database we have hibernate under the hood and we're using Aughrim to map our objects establish Repository package, touch interface files for each entity, write in it: public interface LinkRepository extends JpaRepository<Link,Long> { } We don't need to implement the repository interface, spring does it at runtime
Use JPA Mapping Annotations @OneToMany(mappedBy = "link") 1 link has many
some features: time for create and update, who create or update a bunch of classes needs to be auditable, we can create an abstract class called Auditable and make it a mappeed super class, just extend this class for those classes needing auditable features. This Auditable class doesn't not have a table, the auditable features are within the domain object
Data sourse: spring.datasource.[data-username/password...] Web console: link to H2 in-memory database
connect to h2 h2 datasource setting in application.properties: spring.datasource.url=jdbc:h2:mem:springit spring.datasource.username=sa spring.datasource.password=
spring.jpa.hibernate.ddl-auto=create spring.datasource.url=jdbc:mysql://localhost:3306/springit?allowPublicKeyRetrieval=true&useSSL=false spring.datasource.username=springit spring.datasource.password= in mysql workbench: add user, give privilege execute: use springit; select * from link/comment/vote
turn on in application.properties spring.datasource.initialization-mode=always then add in resources: schema.sql for creating/dropping the db, data.sql for writting some data into the db
first method: 2 CLRs, 1 for database initialization, the other for doing something else, all in com.lunz.springit use @Order(num) to schedule the order for these 2 CLRs
second method (for this project): use bean in SpringitApplication
define a method in repository just follows the query scheme e.g. Link findByTitle(String title) in LinkRepository
handles HTTP request and mapping, move the user to the correct view, or return the correct response in an API call the controller package should be under the main folder use @Controller for match template html file in resources/templates use @RestController without a template @RequestMapping(urlpath) to map the http request to the method @GetMapping = @RequestMapping + GET
pass data down to our view @Controller public class HomeController { @GetMapping("/home_page") // the url for mapping public String home(Model model, HttpServletRequest request){ model.addAttribute("title","Hello, Thymeleaf!"); return "home"; // "home.html" template } }
Welcome page Spring Boot serves static content from a directory called /static (or /public or /resources or /META-INF/resources) in the classpath it firstly looks for index.html in above static content, it's automatically used as the welcome page if found
Custom Favicon favicon.ico in the configured static content website favicon.io for making favicons using png figure Note:!!! placing the figure: note the path shouldn't include special chars favicon.ico didn't work...
ViewResolver template engine: Thymeleaf
use the data attribute for displaying dynamic features: display the title:
read the head from database, add them as model attributes and pass them down to the templates for most of the pages we have same header
<title th:text="${title}">Springit - Spring Boot Reddit Clone</title> the default title when no title attribute passed from controller , the pageTitle is passed down to the layout through attribute addition of "title" in linkControllersave a bunch of links (title and url) to linkRepository in bootstrap/DatabaseLoader Pretty time - library for recording precice time in html, ${link.id} this helper method just call a link domain object
comment,username -> Spring security
if (link.isPresent()) { model.addAttribute("link",link.get()); return "link/view"; } else { return "redirect:/"; }
2 handler method: one to show the page, one to handle the form submission validation: in the domain object class: @NotEmpty(message = "Please enter a url.") @URL(message = "Please enter a valid url.")
spring boot 2 avoids the orders of configuration for security once add the spring-boot-security dependency, by default we will access the login page (everything else is locked down by this page), but we can use the security password in generated console to log in
http .authorizeRequests() .antMatchers("/").permitAll() //everyone has access to home .antMatchers("/link/submit").hasRole("ADMIN"); // only ADMIN has access to submit page .and() .formLogin(); //enable log in to /submit through a login page, still needs the role
in application.properties, spring.security.user.roles=ADMIN Note: if change the access and it can't take effect on the browser, clear the coookies in 开发者工具/Cookies
authentication create a User class in domain/, limit the size and uniqueness of username and password
authorization: Role
override the configure method from our WebSecurityConfigurerAdapter in security/SecurityConfiguration then actually create the implementation in security/UserDetailsServiceImpl
password encryptor - use Bcrypt by Spring Repository stores the encrypted password, but we can use the actual password to login
(User) SecurityContextHolder.getContext().getAuthentication().getPrincipal()) return the current user Note: if we put some existing rows to the repo, remember they don't have login user, so it can't be got by auditing, the resolution is to return a default email if there is no login user
http .requestMatchers(EndpointRequest.to("info")).permitAll() // easy way to get to that endpoint .requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ADMIN") .antMatchers("/actuator/").hasRole("ACTUATOR")
management.endpoint.health.show-details=when_authorized so in /actuator, the info endpoint is open to everyone, but other endpoints need authorization to access
let anyone to access /h2-console/**
current login form: default from spring security now create a custom login form - spring security
new controller to map "templates/auth/login.html"
http.usernameParameter("email"); templates/auth/login.html
after adding CSRF, we can only use HTTP post to log out type an URL in the address bar, this is a GetMapping, can't allow us to log out make a change in the main layout to use a post instead of a get http.logout();
send a cookie to the browser for automatic login 2 implementations (1) hashing to preserve the security of cookie-based tokens (our method) (2) use database or other persistent storage to store generated tokens 2 cookies (1)JsessionID (2)rememberme http.rememberMe()
the Spring Security integration module works as a replacement of the Spring security taglib The sec:authorize attribute renders its content when the attribute expression is evaluated to true use dialect to seperate the content for login and logout state sign in -> submit, account, sign-out sign out -> sign-in, register
The sec:authorize attribute renders its content when the attribute expression is evaluated to true:
Logged user: Bob Roles: [ROLE_USER, ROLE_ADMIN]
set up a connect between the user and link class update 2 places: (1) list.html (2) view.html class="author" th:text="${link.createdBy}">Lunz
/profile -> account /register -> register getmapping + load templates
score = upvotes - downvotes
short direction in Vote; +1/-1
@ManytoOne List votes; int voteCount in Link; each time update the voteCount in our db
@GetMapping("/vote/link/{linkID}/direction/{direction}/votecount/{voteCount}") get the link by ID through linkRepository create a new Vote object with direction defined and save it to voteRepository update the linkCount in object link and save it to linkRepository
call the vote controller method through clicking an area add a script in templates/link/list.html: select all upvote and downvote classes, apply event listener ('click') get id for link get direction from the up/down vote class get voteCount from link object through linkid url = combination of linkid, direction, voteCountvalue call our votecontroller API through fetch(url) print the voteCountValue to the screen
not login but click up/down vote - cause login through "anonymous", add that condition to AuditorAwareImpl to do: (1) user interface side, somebody can't vote if they don't log in Thymeleaf Spring Security Dialect // <script sec:authorize="hasRole('USER')"> as long as a user is authenticated, the script would be executed then if not login and click the vote, there is no eventlistener but this can't protect the url
(2) @Secured({"ROLE_USER"}) before getmapping in votecontroller
add comments to db in commandline runner (Dbloader)
add getPrettytime in Comment domain loop to display each comment in templates/link/view.html
the user need the USER_ROLE to see the add comment option like submitting new link, need to @PostMapping("/link/comments") to get the binding result from view.html into our repo in linkController
Business Logic shouldn't be in the concise controller, instead it should included in the service layer Service class for registration process in the next section
registration functionality: new user, activation email not call repo directly in the controller, instead should go to the service
service class includes a lot of method in repo @Service
integrate all repo method needed into service class, service class can also have other fucntionality, we just want to put all those business logic in one class
like try catch, if error then roll back, can be applied to either a class or a method
user refactory - user info, tie user to a link, registration form to refresh the user repo, spring boot email integration (send email for activation or welcome), activation process
name, alias, databaseloader adaptation when create a link, link it to a user when create a comment, link it to a user
authcontroller model.addAttribute("user",new User()) register.html link the user object and fill different fields To do: confirm password (transient object) click the "Register" button, if some fields are wrong (like empty), display validation errors, if everything is OK, go into the registration process
write a controller that will register our new user registerNewUser model.addAttribute("user",user), so when the user inputs not the entire fields, this object wouldn't be recreated import org.springframework.validation.BindingResult; get the validation error from bindingresult these error comes from annotation message like @NonNull(message="") import org.springframework.ui.Model; add the error to the model attribute
register use register method from userService in authController write register method in userService
To do: figure out why user.enabled is essential to user login
create our own pswd validator confirm password (transient object) domain/validator @PasswordsMatch in User domain object when save the user there is still this validation so set the confirm pswd as the encrypted pswd again
create the activation code (maybe with expire date) send email to user with activation code user click the activation link -> user.enabled = true
local email server: MailDev, built on Node.js org.springframework.boot spring-boot-starter-mail MailService
normal senario: pass data from controller to templates in this case: mailService pass a User instance into the templates templates/email/activation[welcome].html
set activation code in UserService user.setActivationCode(UUID.randomUUID().toString());
getmapping activate in authController, during this process set user.enabled = true and send welcome message welcome page template: auth/activated.html
Compute: Elastic Beanstalk - launch the app
Database: RDS
Networking & Content Delivery Route53 - add domain name
Security IAM
package into .jar ./mvnw clean package upload it to Elastic Beanstalk
Production database RDS edit inbound rules everywhere(Ipv4)
3 versions of application.properties original(not including db setting),dev,prod in loc env search for "edit configuration", add profile version to the "active profile" like dev,aws,prod
in aws env enter Elastic Beanstalk/env/conifg/software/environment SPRING_PROFILES_ACTIVE = prod SPRING_DATASOURCE_USERNAME = lunz SPRING_DATASOURCE_PASSWORD = Danshengou4wo
simple email service (SES) -> SMTP settings Problems: SES not usable ? gmail can use but need to set lts Amazon RDS db usable not viewable (not a problem) submit page error - when user submit, the link doesn't add this user object to its domain, solved through setuser in linkcontroller postmapping, there is a similar issue for commenting as well
todo: 1.upvote/downvote? solved by copy the code from list.html to view.html 2.email test.
route53