Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,47 @@
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
</dependency>
<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
<version>5.5.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
<version>0.11.5</version>
</dependency>



<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
106 changes: 47 additions & 59 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,59 +1,47 @@

# Coded Academy: Student Submission Guidelines

## Online Ordering Server

## Getting Started

### 1. Fork the Repository

1. Click the "Fork" button at the top right of this repository.
2. This will create a copy of the repository in your personal GitHub account.

### 2. Clone Your Forked Repository

### 3. Complete Your Assignment

### 4. Commit Your Changes (Include Task Name in the comment)

Example: "Online Ordering - Post an Order"

### 5. Push Your Branch to Your Fork


### 6. Submit a Pull Request (PR)


## Best Practices

- Write clean, readable code
- Include comments explaining your logic
- Test your code thoroughly
- Follow the coding standards provided in class



## Submission Checklist

- [ ] Forked the repository
- [ ] Completed all assignment requirements
- [ ] Committed changes with descriptive messages
- [ ] Pushed branch to your fork
- [ ] Submitted a pull request

## Need Help?

If you encounter any issues or have questions:
- Check the course materials
- Consult the documentation
- Ask your instructor or teaching assistants

## Code of Conduct

- Be respectful
- Collaborate professionally
- Maintain academic integrity

---

Happy Coding! 🚀👩‍💻👨‍💻
### project navi/info

`src/main/kotlin/com.coded.spring.ordering` is where all the packages are

`dtos` ←→ `controller` → `service` → `repository` → `entity`

`controller` = entry point for http requests (receive requests, process data, determine response)
- `GET`: retrieve data from a server
- `POST`: send data to the server to create a new resource
- `PUT`: update or replace an existing resource
- `DELETE`: remove a resource from the server

`service` = business logic (processes data, makes decisions, eg: save order)

`repository` = database operations (handles communication with db: save, find, delete)

`entity` = domain model (defines what your data is in the system: fields, relationships)

`dto` = data transfer object (cleans request/response json bodies, to avoid returning entities)

### services
- welcome page
- list all orders
- create new order

### progress
#### ultimate goal is to catch up!!!!
- [x] exercise 1: welcome
- [x] bonus: checked with `curl` command
- [x] exercise 2: endpoint to `POST` order
- [x] bonus: add `createdAt` column and sort
- [X] exercise 3: create + connect db
- [x] bonus: create `items` table and connect it to `orders`
- [x] exercise 4: user authentication
- [x] bonus: password validation so people can't create a weak password
- [ ] exercise 5: user profiles
- [ ] bonus:
- [ ] exercise 6: unit testing
- [ ] bonus:
- [ ] exercise 7: menu endpoint
- [ ] bonus:
- [ ] exercise 8: configuration
- [ ] bonus:
- [ ] exercise 9: setup swagger
- [ ] bonus:
- [ ] exercise 10: refactor to micro services
- [ ] bonus:
2 changes: 1 addition & 1 deletion src/main/kotlin/com/coded/spring/ordering/Application.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ class Application

fun main(args: Array<String>) {
runApplication<Application>(*args)
}
}
7 changes: 7 additions & 0 deletions src/main/kotlin/com/coded/spring/ordering/CustomExceptions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.coded.spring.ordering

open class OrderException(message: String) : RuntimeException(message)

class UserIdNotFound(userId: Long): OrderException("User ID $userId not found.")
class InvalidPasswordException(reason: String): OrderException("Invalid password: $reason")
class UsernameAlreadyExistsException(): OrderException("Username already exists.")
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.coded.spring.ordering

import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*

@RestControllerAdvice
class GlobalExceptionHandler {

@ExceptionHandler(OrderException::class)
fun handleOrderingException(ex: OrderException): ResponseEntity<Map<String, String>> {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(mapOf("error" to ex.message!!))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.coded.spring.ordering.authentication

import com.coded.spring.ordering.dto.AuthenticationRequest
import com.coded.spring.ordering.dto.AuthenticationResponse
import com.coded.spring.ordering.dto.FailureResponse
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.security.authentication.*
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping()
class AuthenticationController(
private val authenticationManager: AuthenticationManager,
private val userDetailsService: UserDetailsService,
private val authenticationService: AuthenticationService
) {

@PostMapping("/login")
fun login(@RequestBody authRequest: AuthenticationRequest): ResponseEntity<*> {
return try {
val authToken = UsernamePasswordAuthenticationToken(authRequest.username, authRequest.password)
val authentication = authenticationManager.authenticate(authToken)

if (authentication.isAuthenticated) {
val userDetails = userDetailsService.loadUserByUsername(authRequest.username)
val token = authenticationService.generateToken(userDetails.username)
ResponseEntity.ok(AuthenticationResponse(token))
} else {
throw UsernameNotFoundException("Invalid user request!")
}
} catch (e: BadCredentialsException) {
ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(FailureResponse("Invalid username or password."))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.coded.spring.ordering.authentication

import jakarta.servlet.FilterChain
import jakarta.servlet.http.*
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource
import org.springframework.stereotype.Component
import org.springframework.web.filter.OncePerRequestFilter
import kotlin.text.startsWith
import kotlin.text.substring

@Component
class AuthenticationFilter(
private val authenticationService: AuthenticationService,
private val userDetailsService: UserDetailsService
) : OncePerRequestFilter() {

override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val authHeader = request.getHeader("Authorization")
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response)
return
}

val token = authHeader.substring(7)
val username = authenticationService.extractUsername(token)

if (SecurityContextHolder.getContext().authentication == null) {
val userDetails = userDetailsService.loadUserByUsername(username)
if (authenticationService.isTokenValid(token, userDetails.username)) {
val authToken = UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.authorities
)
authToken.details = WebAuthenticationDetailsSource().buildDetails(request)
SecurityContextHolder.getContext().authentication = authToken
}
}

filterChain.doFilter(request, response)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.coded.spring.ordering.authentication

import io.jsonwebtoken.*
import io.jsonwebtoken.security.Keys
import org.springframework.stereotype.Component
import java.util.*
import javax.crypto.SecretKey

@Component
class AuthenticationService {

private val secretKey: SecretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256)
private val expirationMs: Long = 1000 * 60 * 60 // milliseconds * seconds * minutes = 1 hour

fun generateToken(username: String): String {
val now = Date()
val expiry = Date(now.time + expirationMs)

return Jwts.builder()
.setSubject(username)
.setIssuedAt(now)
.setExpiration(expiry)
.signWith(secretKey)
.compact()
}

fun extractUsername(token: String): String =
Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.body
.subject

fun isTokenValid(token: String, username: String): Boolean {
return try {
extractUsername(token) == username
} catch (e: Exception) {
false
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.coded.spring.ordering.authentication

import com.coded.spring.ordering.repository.UserRepository
import org.springframework.security.core.userdetails.*
import org.springframework.stereotype.Service
import org.springframework.security.core.userdetails.User

@Service
class CustomUserDetailsService(
private val userRepository: UserRepository): UserDetailsService {

override fun loadUserByUsername(username: String): UserDetails {
val user = userRepository.findByUsername(username)
?: throw UsernameNotFoundException("User not found")

return User.builder()
.username(user.username)
.password(user.password)
.authorities(emptyList())
.build()
}
}
Loading