diff --git a/WelcomeToOnlineOrdering.kt b/WelcomeToOnlineOrdering.kt new file mode 100644 index 0000000..c7f0e8e --- /dev/null +++ b/WelcomeToOnlineOrdering.kt @@ -0,0 +1,15 @@ +package com.coded.spring.ordering + +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController + +@RestController + +class WelcomeToOnlineOrdering { + + @GetMapping("/") + fun welcome(): String{ + return "Your Order Await" + } + +} \ No newline at end of file diff --git a/main/kotlin/com/coded/spring/ordering/Application.kt b/main/kotlin/com/coded/spring/ordering/Application.kt new file mode 100644 index 0000000..1b29802 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/Application.kt @@ -0,0 +1,20 @@ +package com.coded.spring.ordering + +import org.springframework.boot.CommandLineRunner +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.autoconfigure.domain.EntityScan +import org.springframework.boot.runApplication +import org.springframework.context.annotation.Bean +import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.security.crypto.password.PasswordEncoder + +@SpringBootApplication +//@EntityScan("com.coded.spring.ordering") // <- your package +//@EnableJpaRepositories(basePackages = ["com.coded.spring.ordering"]) + + +class Application + +fun main(args: Array) { + runApplication(*args) +} diff --git a/main/kotlin/com/coded/spring/ordering/InitUserRunner.kt b/main/kotlin/com/coded/spring/ordering/InitUserRunner.kt new file mode 100644 index 0000000..eeaae4f --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/InitUserRunner.kt @@ -0,0 +1,36 @@ +package com.coded.spring.ordering + +import com.coded.spring.ordering.user.UserEntity +import com.coded.spring.ordering.user.UsersRepository +import org.springframework.boot.CommandLineRunner +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.context.annotation.Bean +import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.boot.runApplication +import org.springframework.stereotype.Component + +//@SpringBootApplication +//@Component +//class InitUserRunner { +// @Bean +// fun initUsers(userRepository: UsersRepository, passwordEncoder: PasswordEncoder) = CommandLineRunner { +// val user = UserEntity( +// id =null, +// name ="Fatma", +// age = 23, +// username = "FFN", +// password = passwordEncoder.encode("123"), +// +// ) +// if (userRepository.findByUsername(user.username) == null) { +// println("Creating user ${user.username}") +// userRepository.save(user) +// } else { +// println("User ${user.username} already exists") +// } +// } +//} + +//fun main(args: Array) { + // runApplication(*args).close() +//} \ No newline at end of file diff --git a/main/kotlin/com/coded/spring/ordering/WelcomeToOnlineOrdering.kt b/main/kotlin/com/coded/spring/ordering/WelcomeToOnlineOrdering.kt new file mode 100644 index 0000000..f015e42 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/WelcomeToOnlineOrdering.kt @@ -0,0 +1,15 @@ +package com.coded.spring.ordering + +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController + +@RestController + +class WelcomeToOnlineOrdering { + + @GetMapping("/hello") + fun welcome(): String{ + return "Your Order Await" + } + +} \ No newline at end of file diff --git a/main/kotlin/com/coded/spring/ordering/auth/AuthenticationController.kt b/main/kotlin/com/coded/spring/ordering/auth/AuthenticationController.kt new file mode 100644 index 0000000..c013036 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/auth/AuthenticationController.kt @@ -0,0 +1,43 @@ +package com.coded.spring.ordering.auth + + +import org.springframework.security.authentication.AuthenticationManager +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.core.userdetails.UsernameNotFoundException +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/auth") +class AuthenticationController( + private val authenticationManager: AuthenticationManager, + private val userDetailsService: UserDetailsService, + private val jwtService: JwtService +) { + + @PostMapping("/login") + fun login(@RequestBody authRequest: AuthenticationRequest): AuthenticationResponse { + val authToken = UsernamePasswordAuthenticationToken(authRequest.username, authRequest.password) + val authentication = authenticationManager.authenticate(authToken) + + if (authentication.isAuthenticated) { + val userDetails = userDetailsService.loadUserByUsername(authRequest.username) + val token = jwtService.generateToken(userDetails.username) + return AuthenticationResponse(token) + } else { + throw UsernameNotFoundException("Invalid user request!") + } + } +} + +data class AuthenticationRequest( + val username: String, + val password: String +) + +data class AuthenticationResponse( + val token: String +) diff --git a/main/kotlin/com/coded/spring/ordering/auth/CustomUserDetailsService.kt b/main/kotlin/com/coded/spring/ordering/auth/CustomUserDetailsService.kt new file mode 100644 index 0000000..ecc28ec --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/auth/CustomUserDetailsService.kt @@ -0,0 +1,34 @@ +package com.coded.spring.ordering.auth + + +import com.coded.spring.ordering.user.UserEntity +import com.coded.spring.ordering.user.UsersRepository +import org.springframework.security.core.userdetails.User + +import org.springframework.security.core.userdetails.UserDetails +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.core.userdetails.UsernameNotFoundException +import org.springframework.stereotype.Service + +@Service +//@Service +class CustomerUserDetailsService( + private val usersRepository: UsersRepository +): UserDetailsService { + + override fun loadUserByUsername(username: String): UserDetails { + + val user: UserEntity = + usersRepository.findByUsername(username) ?: throw UsernameNotFoundException("User not found...") + + + return User.builder() + .username(user.username) + .password(user.password) + .authorities("USER") + .build() + + + } + +} \ No newline at end of file diff --git a/main/kotlin/com/coded/spring/ordering/auth/JwtAuthenticationFilter.kt b/main/kotlin/com/coded/spring/ordering/auth/JwtAuthenticationFilter.kt new file mode 100644 index 0000000..62cbec9 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/auth/JwtAuthenticationFilter.kt @@ -0,0 +1,45 @@ +package com.coded.spring.ordering.auth + +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 + +@Component +class JwtAuthenticationFilter( + private val jwtService: JwtService, + 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 = jwtService.extractUsername(token) + + if (SecurityContextHolder.getContext().authentication == null) { + if (jwtService.isTokenValid(token, username)) { + val userDetails = userDetailsService.loadUserByUsername(username) + val authToken = UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.authorities + ) + authToken.details = WebAuthenticationDetailsSource().buildDetails(request) + SecurityContextHolder.getContext().authentication = authToken + } + } + + filterChain.doFilter(request, response) + } +} \ No newline at end of file diff --git a/main/kotlin/com/coded/spring/ordering/auth/JwtService.kt b/main/kotlin/com/coded/spring/ordering/auth/JwtService.kt new file mode 100644 index 0000000..3b74f26 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/auth/JwtService.kt @@ -0,0 +1,43 @@ +package com.coded.spring.ordering.auth +import jakarta.inject.Named + +import io.jsonwebtoken.Jwts +import io.jsonwebtoken.SignatureAlgorithm +import io.jsonwebtoken.security.Keys +import org.springframework.stereotype.Component +import java.util.Date +import javax.crypto.SecretKey +@Named +@Component +class JwtService { + + private val secretKey: SecretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256) + private val expirationMs: Long = 1000 * 60 * 60 + + 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 + } + } +} diff --git a/main/kotlin/com/coded/spring/ordering/auth/SecurityConfig.kt b/main/kotlin/com/coded/spring/ordering/auth/SecurityConfig.kt new file mode 100644 index 0000000..1f27242 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/auth/SecurityConfig.kt @@ -0,0 +1,58 @@ +package com.coded.spring.ordering.auth + +import com.coded.spring.ordering.auth.JwtAuthenticationFilter +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.authentication.AuthenticationManager +import org.springframework.security.authentication.AuthenticationProvider +import org.springframework.security.authentication.dao.DaoAuthenticationProvider +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.config.http.SessionCreationPolicy +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder +import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.security.web.SecurityFilterChain +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter + + + +@Configuration +@EnableWebSecurity +class SecurityConfig( + private val jwtAuthFilter: JwtAuthenticationFilter, + private val userDetailsService: UserDetailsService +) { + + @Bean + fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http.csrf { it.disable() } + .authorizeHttpRequests { + it.requestMatchers("/auth/**").permitAll() + .anyRequest().authenticated() + } + .sessionManagement { + it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) + } + .authenticationProvider(authenticationProvider()) + .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter::class.java) + + return http.build() + } + + @Bean + fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder() + + @Bean + fun authenticationManager(config: AuthenticationConfiguration): AuthenticationManager = + config.authenticationManager + + @Bean + fun authenticationProvider(): AuthenticationProvider { + val provider = DaoAuthenticationProvider() + provider.setUserDetailsService(userDetailsService) + provider.setPasswordEncoder(passwordEncoder()) + return provider + } +} \ No newline at end of file diff --git a/main/kotlin/com/coded/spring/ordering/item/ItemDTO.kt b/main/kotlin/com/coded/spring/ordering/item/ItemDTO.kt new file mode 100644 index 0000000..1398037 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/item/ItemDTO.kt @@ -0,0 +1,17 @@ +package com.coded.spring.ordering.item + +data class Item( + val id: Long?, + val order_id: Long?, + val name: String?, + val quantity: Long?, + val note: String?, + val price: Double? +) + +data class SubmitItemRequest( + val name: String, + val quantity: Long, + val note: String?, + val price: Double +) \ No newline at end of file diff --git a/main/kotlin/com/coded/spring/ordering/item/ItemEntity.kt b/main/kotlin/com/coded/spring/ordering/item/ItemEntity.kt new file mode 100644 index 0000000..b48da41 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/item/ItemEntity.kt @@ -0,0 +1,29 @@ +package com.coded.spring.ordering.item + +import com.coded.spring.ordering.order.OrderEntity +import jakarta.persistence.* + +@Entity +@Table(name = "items") +data class ItemEntity( + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null, + + @Column(name = "items") + var name: String? = null, + + var quantity: Long? = null, + + var note: String? = null, + + var price: Double? = null, + + @ManyToOne + @JoinColumn(name = "order_id") + var order: OrderEntity? = null + +) { + constructor() : this(null, "", 1, "", 0.0, null) +} \ No newline at end of file diff --git a/main/kotlin/com/coded/spring/ordering/item/ItemRepo.kt b/main/kotlin/com/coded/spring/ordering/item/ItemRepo.kt new file mode 100644 index 0000000..d0f24cb --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/item/ItemRepo.kt @@ -0,0 +1,5 @@ +package com.coded.spring.ordering.item + +import org.springframework.data.jpa.repository.JpaRepository + +interface ItemsRepository : JpaRepository \ No newline at end of file diff --git a/main/kotlin/com/coded/spring/ordering/item/ItemServ.kt b/main/kotlin/com/coded/spring/ordering/item/ItemServ.kt new file mode 100644 index 0000000..370ba05 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/item/ItemServ.kt @@ -0,0 +1,16 @@ +package com.coded.spring.ordering.item + +import jakarta.inject.Named + +@Named +class ItemsServ(private val itemsRepository: ItemsRepository) { + fun listItems(): List = itemsRepository.findAll().map { entity -> + Item(id = entity.id, order_id = entity.order?.id, name = entity.name, quantity = entity.quantity, note = entity.note, price = entity.price) + } + + fun submitItem(request: SubmitItemRequest): ItemEntity { + val item = ItemEntity(name = request.name, quantity = request.quantity, note = request.note, price = request.price) + return itemsRepository.save(item) + } + +} \ No newline at end of file diff --git a/main/kotlin/com/coded/spring/ordering/item/ItemsController.kt b/main/kotlin/com/coded/spring/ordering/item/ItemsController.kt new file mode 100644 index 0000000..071ecac --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/item/ItemsController.kt @@ -0,0 +1,25 @@ +package com.coded.spring.ordering.item + +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RestController + + +@RestController + + +class ItemsController(private val itemsService: ItemsServ) { + + + @GetMapping("/listItems") + fun listItems(): List = itemsService.listItems() + + + + @PostMapping("/submitItems") + fun submitItem(@RequestBody request: SubmitItemRequest): ItemEntity { + return itemsService.submitItem(request) + } + +} \ No newline at end of file diff --git a/main/kotlin/com/coded/spring/ordering/menu/MenuController.kt b/main/kotlin/com/coded/spring/ordering/menu/MenuController.kt new file mode 100644 index 0000000..bd386f0 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/menu/MenuController.kt @@ -0,0 +1,12 @@ +package com.coded.spring.ordering.menu + +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController + + +@RestController +class MenuController( + private val menuService: MenuServ +) { + @GetMapping("/menus/v1/menu") + fun getMenu(): List = menuService.getMenu()} diff --git a/main/kotlin/com/coded/spring/ordering/menu/menuEntity.kt b/main/kotlin/com/coded/spring/ordering/menu/menuEntity.kt new file mode 100644 index 0000000..17e98c9 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/menu/menuEntity.kt @@ -0,0 +1,16 @@ +package com.coded.spring.ordering.menu + +import jakarta.persistence.* + +@Entity +@Table(name = "menu") +data class MenuEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + val name: String, + val description: String, + val price: Double +) { + constructor() : this(null, "", "", 0.0) +} diff --git a/main/kotlin/com/coded/spring/ordering/menu/menuRepo.kt b/main/kotlin/com/coded/spring/ordering/menu/menuRepo.kt new file mode 100644 index 0000000..83daba6 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/menu/menuRepo.kt @@ -0,0 +1,7 @@ +package com.coded.spring.ordering.menu + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface MenuRepository : JpaRepository \ No newline at end of file diff --git a/main/kotlin/com/coded/spring/ordering/menu/menuServ.kt b/main/kotlin/com/coded/spring/ordering/menu/menuServ.kt new file mode 100644 index 0000000..4eace2e --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/menu/menuServ.kt @@ -0,0 +1,9 @@ +package com.coded.spring.ordering.menu + +//import org.springframework.stereotype.Service +import jakarta.inject.Named +@Named +//@Service +class MenuServ(private val menuRepository: MenuRepository) { + fun getMenu(): List = menuRepository.findAll() +} \ No newline at end of file diff --git a/main/kotlin/com/coded/spring/ordering/order/OrderCon.kt b/main/kotlin/com/coded/spring/ordering/order/OrderCon.kt new file mode 100644 index 0000000..41e9757 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/order/OrderCon.kt @@ -0,0 +1,33 @@ +package com.coded.spring.ordering.order + + +//import org.hibernate.query.Order +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import java.security.Principal + +@RestController +@RequestMapping("/orders/v1") +class OrderController(val ordersRepository: OrdersRepository, private val ordersService: OrdersService){ + + @PostMapping + fun submitOrder( + // Extracts the logged-in user's details + @RequestBody request: SubmitOrderRequest, principal: Principal): ResponseEntity { + + val username = principal.name // Fetch the logged-in user's username + + ordersService.submitOrder(username, request.itemIds) + + return ResponseEntity.ok(" submitted.") + } + + @GetMapping + //list all orders for the logged-in user + fun listMyOrders(principal: Principal): List { + return ordersService.listOrdersForUser(principal.name) // Call the service to get all orders for the logged-in user + + } + + +} diff --git a/main/kotlin/com/coded/spring/ordering/order/OrderEntity.kt b/main/kotlin/com/coded/spring/ordering/order/OrderEntity.kt new file mode 100644 index 0000000..656e238 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/order/OrderEntity.kt @@ -0,0 +1,25 @@ +package com.coded.spring.ordering.order + +import com.coded.spring.ordering.item.ItemEntity +import com.coded.spring.ordering.user.UserEntity +import jakarta.persistence.* + + +@Entity +@Table(name = "orders") +class OrderEntity( + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null, + + @ManyToOne + @JoinColumn(name = "user_id") + var user: UserEntity? = null, + + @OneToMany(mappedBy = "order", cascade = [CascadeType.ALL]) + var items: List? = null + +) { + constructor() : this(null, null, listOf()) +} \ No newline at end of file diff --git a/main/kotlin/com/coded/spring/ordering/order/OrdersService.kt b/main/kotlin/com/coded/spring/ordering/order/OrdersService.kt new file mode 100644 index 0000000..6959284 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/order/OrdersService.kt @@ -0,0 +1,48 @@ +package com.coded.spring.ordering.order + +import com.coded.spring.ordering.item.Item +import com.coded.spring.ordering.item.ItemsRepository +import com.coded.spring.ordering.user.UsersRepository +import jakarta.inject.Named + +@Named +class OrdersService( + private val ordersRepository: OrdersRepository, + private val usersRepository: UsersRepository, + private val itemsRepository: ItemsRepository +) { + + fun submitOrder(username: String, itemIds: List) { + val user = usersRepository.findByUsername(username) + ?: throw IllegalArgumentException("User not found") // If the user doesn't exist, throw an exception + + + val order = OrderEntity(user = user) // Create a new order for the user + val savedOrder = ordersRepository.save(order) + + + // Get all items from the database using the provided item IDs + val items = itemsRepository.findAllById(itemIds).map { item -> + // Assign the newly created order to each item + item.order = savedOrder + item + } + + itemsRepository.saveAll(items) + } + + + //get all orders for a specific user + fun listOrdersForUser(username: String): List { + val user = usersRepository.findByUsername(username) + ?: throw IllegalArgumentException("User not found") + + // Get all orders that belong to the user based on their user ID + return ordersRepository.findByUserId(user.id!!).map { orderEntity -> + Order(id = orderEntity.id, user_id = user.id, items = orderEntity.items?.map { + Item(id = it.id, order_id = it.order?.id, name = it.name, quantity = it.quantity, note = it.note, price = it.price) + } ?: listOf() // If there are no items, return an empty list + ) + } + } +} diff --git a/main/kotlin/com/coded/spring/ordering/order/orderDTO.kt b/main/kotlin/com/coded/spring/ordering/order/orderDTO.kt new file mode 100644 index 0000000..1246eca --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/order/orderDTO.kt @@ -0,0 +1,22 @@ +package com.coded.spring.ordering.order + +import com.coded.spring.ordering.item.Item +data class SubmitOrderRequest( + val itemIds: List +) + +data class Order( + val id: Long?, + val user_id: Long?, + val items: List +) + +//testing +data class CreateOrderResponse( + val orderId: Long, + val items: List ) + +data class CreateOrderRequest( + val userId: Long, + val items: List +) \ No newline at end of file diff --git a/main/kotlin/com/coded/spring/ordering/order/orderRepo.kt b/main/kotlin/com/coded/spring/ordering/order/orderRepo.kt new file mode 100644 index 0000000..b5cbfae --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/order/orderRepo.kt @@ -0,0 +1,10 @@ +package com.coded.spring.ordering.order + + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface OrdersRepository: JpaRepository{ + fun findByUserId(userId: Long): List +} \ No newline at end of file diff --git a/main/kotlin/com/coded/spring/ordering/profile/ProfileCon.kt b/main/kotlin/com/coded/spring/ordering/profile/ProfileCon.kt new file mode 100644 index 0000000..291b780 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/profile/ProfileCon.kt @@ -0,0 +1,48 @@ +package com.coded.spring.ordering.profile + +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import java.security.Principal + + +@RestController +@RequestMapping("/profile") +class ProfileController( + private val profileService: ProfileService +) { + + @PostMapping + fun submitProfile( + @RequestBody request: ProfileRequest, + principal: Principal + ): ResponseEntity { + return try { + // Call the profile service to save the profile for the authenticated user + profileService.saveProfile(principal.name, request) + ResponseEntity.ok(mapOf("message" to "Profile created successfully.")) + } catch (e: IllegalArgumentException) { + // Handle validation errors + ResponseEntity.badRequest().body(mapOf("error" to e.message)) + } catch (e: Exception) { + // Handle other unexpected errors + e.printStackTrace() + ResponseEntity.internalServerError().body(mapOf("error" to "Something went wrong.")) + } + } + + @GetMapping + fun getProfile(principal: Principal): ResponseEntity { + return try { + // Fetch and return the profile of the authenticated user + val profile = profileService.getByUsername(principal.name) + ResponseEntity.ok(profile) + } catch (e: IllegalArgumentException) { + // Handle case when profile is not found + ResponseEntity.badRequest().body(mapOf("error" to e.message)) + } catch (e: Exception) { + // Handle other unexpected errors + e.printStackTrace() + ResponseEntity.internalServerError().body(mapOf("error" to "Something went wrong")) + } + } +} \ No newline at end of file diff --git a/main/kotlin/com/coded/spring/ordering/profile/profileDTO.kt b/main/kotlin/com/coded/spring/ordering/profile/profileDTO.kt new file mode 100644 index 0000000..3636ac1 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/profile/profileDTO.kt @@ -0,0 +1,7 @@ +package com.coded.spring.ordering.profile + +data class ProfileRequest( + val firstName: String, + val lastName: String, + val phoneNumber: String +) \ No newline at end of file diff --git a/main/kotlin/com/coded/spring/ordering/profile/profileEntity.kt b/main/kotlin/com/coded/spring/ordering/profile/profileEntity.kt new file mode 100644 index 0000000..9a14c07 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/profile/profileEntity.kt @@ -0,0 +1,18 @@ +package com.coded.spring.ordering.profile + +import jakarta.persistence.* + +@Entity +@Table(name = "profiles") +data class ProfileEntity( + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long? = null, + + val firstName: String = "", + val lastName: String = "", + val phoneNumber: String = "", + val userId: Long = 0 + +) \ No newline at end of file diff --git a/main/kotlin/com/coded/spring/ordering/profile/profileRepo.kt b/main/kotlin/com/coded/spring/ordering/profile/profileRepo.kt new file mode 100644 index 0000000..2b6ee58 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/profile/profileRepo.kt @@ -0,0 +1,7 @@ +package com.coded.spring.ordering.profile + +import org.springframework.data.jpa.repository.JpaRepository + +interface ProfileRepository : JpaRepository { + fun findByUserId(userId: Long): ProfileEntity? +} diff --git a/main/kotlin/com/coded/spring/ordering/profile/profileServ.kt b/main/kotlin/com/coded/spring/ordering/profile/profileServ.kt new file mode 100644 index 0000000..bd3e7cf --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/profile/profileServ.kt @@ -0,0 +1,79 @@ +package com.coded.spring.ordering.profile + +import com.coded.spring.ordering.user.UsersRepository +import jakarta.transaction.Transactional +import org.springframework.security.core.userdetails.UsernameNotFoundException +import org.springframework.stereotype.Service + +@Service +open class ProfileService( + private val profileRepository: ProfileRepository, + private val usersRepository: UsersRepository +) { + + fun isValidName(name: String): Boolean { + val pattern = "[A-Za-z]{1,}".toRegex() // Checks if a name contains only letters (A-Z or a-z) + at least 1 letter + return pattern.matches(name) // Returns true if name matches the pattern + } + + + // Checks if the phone number is exactly 8 digits long + fun isValidPhone(phone: String): Boolean { + val digitsOnly = Regex("\\d{8}") + return digitsOnly.matches(phone) // true if phone matches the pattern + } + + @Transactional + fun saveProfile(username: String, request: ProfileRequest): ProfileEntity { + val user = usersRepository.findByUsername(username) + ?: throw UsernameNotFoundException("User not found") + + + // Check if the user already has a profile + val existingProfile = profileRepository.findByUserId(user.id!!) + if (existingProfile != null) { + throw IllegalArgumentException("Profile already exists for this user") + } + + + // Check if first name is valid (only letters) + if (!isValidName(request.firstName)) { + throw IllegalArgumentException("First name must contain only letters") + } + + // Check if last name is valid (only letters) + if (!isValidName(request.lastName)) { + throw IllegalArgumentException("Last name must contain only letters") + } + + // Check if phone number is valid (8 digits) + if (!isValidPhone(request.phoneNumber)) { + throw IllegalArgumentException("Phone number must be exactly 8 digits") + } + + + // Create a new profile with the given data + val profile = ProfileEntity( + firstName = request.firstName, + lastName = request.lastName, + phoneNumber = request.phoneNumber, + userId = user.id!! + ) + + return profileRepository.save(profile) // Save the new profile in the DB and return it + } + + + // Get the profile for a user using their username + fun getByUsername(username: String): ProfileEntity { + val user = usersRepository.findByUsername(username) + ?: throw UsernameNotFoundException("User not found") + + + // Get the profile for the user's ID + val profile = profileRepository.findByUserId(user.id!!) + ?: throw IllegalArgumentException("Profile not found for this user") + + return profile + } +} diff --git a/main/kotlin/com/coded/spring/ordering/user/UserEntity.kt b/main/kotlin/com/coded/spring/ordering/user/UserEntity.kt new file mode 100644 index 0000000..aec6d38 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/user/UserEntity.kt @@ -0,0 +1,24 @@ +package com.coded.spring.ordering.user + +import jakarta.persistence.* + +@Entity +@Table(name = "users") +data class UserEntity( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long ?= null, + var name: String, + var age : Int, + var username: String, + var password: String, + + //@Enumerated(EnumType.STRING) + //val role: Roles = Roles.USER +) { + constructor() : this(null,"", 0, "","" ) +} + +//enum class Roles { + // USER, ADMIN +//} \ No newline at end of file diff --git a/main/kotlin/com/coded/spring/ordering/user/UserService.kt b/main/kotlin/com/coded/spring/ordering/user/UserService.kt new file mode 100644 index 0000000..d6a7c30 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/user/UserService.kt @@ -0,0 +1,13 @@ +package com.coded.spring.ordering.user + +import jakarta.inject.Named +@Named +class UsersService( + private val usersRepository: UsersRepository, +) { + + fun listUsers(): List = usersRepository.findAll().map { + User(name = it.name, age = it.age, username = it.username, password = it.password) // Create a new User using the data from the database + } +} + diff --git a/main/kotlin/com/coded/spring/ordering/user/userDTO.kt b/main/kotlin/com/coded/spring/ordering/user/userDTO.kt new file mode 100644 index 0000000..84140cb --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/user/userDTO.kt @@ -0,0 +1,8 @@ +package com.coded.spring.ordering.user + +data class User( + val name: String, + val age: Int, + val username: String, + val password: String +) \ No newline at end of file diff --git a/main/kotlin/com/coded/spring/ordering/user/userRepo.kt b/main/kotlin/com/coded/spring/ordering/user/userRepo.kt new file mode 100644 index 0000000..e0a7bc2 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/user/userRepo.kt @@ -0,0 +1,9 @@ +package com.coded.spring.ordering.user + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface UsersRepository : JpaRepository { + fun findByUsername(username: String): UserEntity? +} diff --git a/main/kotlin/com/coded/spring/ordering/user/usercon.kt b/main/kotlin/com/coded/spring/ordering/user/usercon.kt new file mode 100644 index 0000000..1346433 --- /dev/null +++ b/main/kotlin/com/coded/spring/ordering/user/usercon.kt @@ -0,0 +1,17 @@ +package com.coded.spring.ordering.user + +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController + + +@RestController +class UsersController( + private val usersService: UsersService +) { + + @GetMapping("/welcome") + fun users() = usersService.listUsers() + + + +} \ No newline at end of file diff --git a/main/resources/application.properties b/main/resources/application.properties new file mode 100644 index 0000000..300c0c2 --- /dev/null +++ b/main/resources/application.properties @@ -0,0 +1,7 @@ +spring.application.name=Kotlin.SpringbootV2 + +spring.datasource.url=jdbc:postgresql://localhost:5433/orderingDB +spring.datasource.username=postgres +spring.datasource.password=123 +spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect +spring.jpa.show-sql=true diff --git a/pom.xml b/pom.xml index 163ad53..9b07d50 100644 --- a/pom.xml +++ b/pom.xml @@ -30,11 +30,40 @@ 21 1.9.25 + + + org.springframework.boot + spring-boot-starter-test + test + + + org.jetbrains.kotlin + kotlin-test-junit5 + test + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-actuator + + + io.micrometer + micrometer-tracing-bridge-brave + + org.springframework.boot spring-boot-starter-web + + org.postgresql + postgresql + runtime + com.fasterxml.jackson.module jackson-module-kotlin @@ -47,7 +76,14 @@ org.jetbrains.kotlin kotlin-stdlib - + + org.springframework.boot + spring-boot-starter-data-jpa + + + jakarta.inject + jakarta.inject-api + org.springframework.boot spring-boot-starter-test @@ -58,6 +94,28 @@ kotlin-test-junit5 test + + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + + + io.jsonwebtoken + jjwt-impl + runtime + 0.11.5 + + + + io.jsonwebtoken + jjwt-jackson + runtime + 0.11.5 + + diff --git a/src/main/kotlin/com/coded/spring/ordering/OrderCon.kt b/src/main/kotlin/com/coded/spring/ordering/OrderCon.kt new file mode 100644 index 0000000..2dac92c --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/OrderCon.kt @@ -0,0 +1,29 @@ +package com.coded.spring.ordering + +import org.springframework.web.bind.annotation.* + + +@RestController +@RequestMapping("/orders") +class OrderCon (val orderRepository: OrderRepository){ + + + @PostMapping + fun createOrder(@RequestBody orderDTO: OrderDTO): String { + val order = Order( + user = orderDTO.user, + resturant = orderDTO.resturant, + items = orderDTO.items + ) + orderRepository.save(order) + return "Order submitted successfully" + } + + @GetMapping + fun listOrders(): List { + return orderRepository.findAll() + + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/WelcomeToOnlineOrdering.kt b/src/main/kotlin/com/coded/spring/ordering/WelcomeToOnlineOrdering.kt new file mode 100644 index 0000000..c7f0e8e --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/WelcomeToOnlineOrdering.kt @@ -0,0 +1,15 @@ +package com.coded.spring.ordering + +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController + +@RestController + +class WelcomeToOnlineOrdering { + + @GetMapping("/") + fun welcome(): String{ + return "Your Order Await" + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/order.kt b/src/main/kotlin/com/coded/spring/ordering/order.kt new file mode 100644 index 0000000..7779aeb --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/order.kt @@ -0,0 +1,22 @@ +package com.coded.spring.ordering + +import jakarta.persistence.* + + +@Entity +@Table(name = "orders") +data class Order( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Int = 0, + + @Column(name = "user_id") + val user: String, + + val resturant: String, + + @ElementCollection + val items: List +) { + constructor() : this(0, "", "", listOf()) +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/orderDTO.kt b/src/main/kotlin/com/coded/spring/ordering/orderDTO.kt new file mode 100644 index 0000000..a19974b --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/orderDTO.kt @@ -0,0 +1,7 @@ +package com.coded.spring.ordering + +data class OrderDTO ( + val user: String, + val resturant: String, + val items: List + ) \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/orderRepo.kt b/src/main/kotlin/com/coded/spring/ordering/orderRepo.kt new file mode 100644 index 0000000..a8b648c --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/orderRepo.kt @@ -0,0 +1,8 @@ +package com.coded.spring.ordering + + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface OrderRepository : JpaRepository diff --git a/src/main/kotlin/com/coded/spring/ordering/ue.kt b/src/main/kotlin/com/coded/spring/ordering/ue.kt new file mode 100644 index 0000000..e06b54b --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/ue.kt @@ -0,0 +1,23 @@ +package com.coded.spring.ordering + +import jakarta.persistence.* +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface UserRepository: JpaRepository + + + +@Entity +@Table(name = "users2") +data class ue ( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id : Int, + + var name: String, + var age: Int?, +){ + constructor() : this(0, "", null) +} \ No newline at end of file diff --git a/src/main/kotlin/com/coded/spring/ordering/userco.kt b/src/main/kotlin/com/coded/spring/ordering/userco.kt new file mode 100644 index 0000000..e869e56 --- /dev/null +++ b/src/main/kotlin/com/coded/spring/ordering/userco.kt @@ -0,0 +1,14 @@ +package com.coded.spring.ordering + +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +class userco (val UserRepository : UserRepository) { + @GetMapping("/users2") + fun getall(): List{ + return UserRepository.findAll() + } + +} + diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3704dc6..300c0c2 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,7 @@ spring.application.name=Kotlin.SpringbootV2 + +spring.datasource.url=jdbc:postgresql://localhost:5433/orderingDB +spring.datasource.username=postgres +spring.datasource.password=123 +spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect +spring.jpa.show-sql=true diff --git a/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt b/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt new file mode 100644 index 0000000..53fe853 --- /dev/null +++ b/test/kotlin/com/coded/spring/ordering/ApplicationTests.kt @@ -0,0 +1,131 @@ +package com.coded.spring.ordering + + + + +import com.coded.spring.ordering.auth.JwtService +import com.coded.spring.ordering.item.Item +import com.coded.spring.ordering.order.CreateOrderRequest +import com.coded.spring.ordering.order.CreateOrderResponse +import com.coded.spring.ordering.user.UserEntity +import com.coded.spring.ordering.user.UsersRepository +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.web.client.TestRestTemplate +import org.springframework.http.HttpEntity +import org.springframework.http.HttpMethod +import org.springframework.http.HttpStatus +import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.test.context.ActiveProfiles +//import java.net.http.HttpHeaders +import org.springframework.http.HttpHeaders + +//import kotlin.test.junit5.JUnit5Asserter.assertEquals +import org.junit.jupiter.api.Assertions.assertEquals +import org.springframework.util.MultiValueMap + + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("test") +class ApplicationTests { + @Autowired + lateinit var restTemplate: TestRestTemplate + + companion object { + @JvmStatic + @BeforeAll + fun setUp( + @Autowired usersRepository: UsersRepository, + @Autowired passwordEncoder: PasswordEncoder, + ) { + usersRepository.deleteAll() + val testUser = UserEntity( + name = "coded", + age = 23, + username = "coded", + password = passwordEncoder.encode("joincoded") + ) + usersRepository.save(testUser) + println(testUser.id) + } + } + + @Test + fun testHelloWorld(@Autowired jwtService: JwtService) { + val token = jwtService.generateToken("coded") + + val headers = HttpHeaders() + headers.set("Authorization", "Bearer $token") + + //val headers = HttpHeaders( + //MultiValueMap.fromSingleValueMap(mapOf("Authorization" to "Bearer $token")) + //) + + val requestEntity = HttpEntity(headers) + + val result = restTemplate.exchange( + "/hello", + HttpMethod.GET, + requestEntity, + String::class.java + ) + assertEquals(HttpStatus.OK, result.statusCode) + assertEquals("Your Order Await", result.body) + } + + @Test + fun testOrderSubmit(@Autowired jwtService: JwtService) { + val token = jwtService.generateToken("coded") + +// val headers = HttpHeaders() +// headers.set("Authorization", "Bearer $token") + + val headers = HttpHeaders( + MultiValueMap.fromSingleValue(mapOf("Authorization" to "Bearer $token")) + ) + + val body = CreateOrderRequest( + userId = 15, + items = listOf( + Item( + id = null, + order_id = null, + name = "Chicken Burger", + quantity = 3L, + note = null, + price = 5.99 + ) + ) + ) + + val requestEntity = HttpEntity(body, headers) + val actualResponse = restTemplate.exchange( + "/orders/v1/orders", + HttpMethod.POST, + requestEntity, + CreateOrderResponse::class.java + ) + + assertEquals(HttpStatus.OK, actualResponse.statusCode) + + val expectedResponse = CreateOrderResponse( + orderId = 1, + items = listOf( + Item( + id = null, + order_id = null, + name = "Chicken Burger", + quantity = 3L, + note = null, + price = 5.99 + ) + ) + ) + assertEquals(expectedResponse, actualResponse.body, "Unexpected order created...") + } +} + + + diff --git a/test/resources/application.test.properties b/test/resources/application.test.properties new file mode 100644 index 0000000..3faaa70 --- /dev/null +++ b/test/resources/application.test.properties @@ -0,0 +1,11 @@ +spring.application.name=Kotlin.SpringbootV2 + +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect + +# Optional: enable H2 web console for debugging +spring.h2.console.enabled=true \ No newline at end of file