diff --git a/backend/pom.xml b/backend/pom.xml index c36a590..8f100b4 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -30,16 +30,48 @@ 17 - + org.springframework.boot spring-boot-starter - - + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-security + + + org.postgresql + postgresql + + org.springframework.boot spring-boot-starter-test test - + + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + 0.11.5 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.11.5 + runtime + diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/config/SecurityConfig.java b/backend/src/main/java/xyz/mcorangehq/mcinv/config/SecurityConfig.java new file mode 100644 index 0000000..1a2d010 --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/config/SecurityConfig.java @@ -0,0 +1,43 @@ +package xyz.mcorangehq.mcinv.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +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; + +import xyz.mcorangehq.mcinv.security.JwtFilter; +import xyz.mcorangehq.mcinv.security.JwtService; + +@Configuration +public class SecurityConfig { + private final JwtService jwtService; + + public SecurityConfig(JwtService jwtService) { + this.jwtService = jwtService; + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + + http.csrf(csrf -> csrf.disable()); + + http.authorizeHttpRequests(auth -> auth + .requestMatchers("/auth/**").permitAll() + .anyRequest().authenticated() + ); + + http.addFilterBefore( + new JwtFilter(jwtService), + UsernamePasswordAuthenticationFilter.class + ); + return http.build(); + } +} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/config/StartupConfig.java b/backend/src/main/java/xyz/mcorangehq/mcinv/config/StartupConfig.java new file mode 100644 index 0000000..aaa408e --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/config/StartupConfig.java @@ -0,0 +1,41 @@ +package xyz.mcorangehq.mcinv.config; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.password.PasswordEncoder; + +import xyz.mcorangehq.mcinv.model.User; +import xyz.mcorangehq.mcinv.repository.UserRepository; + +@Configuration +public class StartupConfig { + + @Bean + CommandLineRunner createAdmin( + UserRepository repo, + PasswordEncoder encoder + ) { + + return args -> { + + User existing = repo.findByUsername("admin"); + + if (existing != null) { + return; + } + + User user = new User(); + + user.setUsername("admin"); + user.setEmail(""); + user.setPasswordHash( + encoder.encode("admin") + ); + + repo.save(user); + + System.out.println("Created default admin user"); + }; + } +} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/controller/v1/AuthController.java b/backend/src/main/java/xyz/mcorangehq/mcinv/controller/v1/AuthController.java new file mode 100644 index 0000000..723f043 --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/controller/v1/AuthController.java @@ -0,0 +1,23 @@ +package xyz.mcorangehq.mcinv.controller.v1; + +import xyz.mcorangehq.mcinv.model.dto.LoginRequest; +import xyz.mcorangehq.mcinv.service.AuthService; + +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/auth") +public class AuthController { + + private final AuthService service; + + public AuthController(AuthService service) { + this.service = service; + } + + @GetMapping("/login") + public String login(@RequestBody LoginRequest req) { + return service.login(req.getUsername(), req.getPassword()); + } + +} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/controller/v1/ItemMovementLogController.java b/backend/src/main/java/xyz/mcorangehq/mcinv/controller/v1/ItemMovementLogController.java new file mode 100644 index 0000000..df798e9 --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/controller/v1/ItemMovementLogController.java @@ -0,0 +1,24 @@ +package xyz.mcorangehq.mcinv.controller.v1; + +import xyz.mcorangehq.mcinv.model.ItemMovementLog; +import xyz.mcorangehq.mcinv.repository.ItemMovementLogRepository; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/v1/item_movement_log") +public class ItemMovementLogController { + + private final ItemMovementLogRepository repo; + + public ItemMovementLogController(ItemMovementLogRepository repo) { + this.repo = repo; + } + + @GetMapping + public List getAll() { + return repo.findAll(); + } + +} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/controller/v1/ItemTypeController.java b/backend/src/main/java/xyz/mcorangehq/mcinv/controller/v1/ItemTypeController.java new file mode 100644 index 0000000..908d672 --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/controller/v1/ItemTypeController.java @@ -0,0 +1,54 @@ +package xyz.mcorangehq.mcinv.controller.v1; + +import xyz.mcorangehq.mcinv.model.ItemType; +import xyz.mcorangehq.mcinv.repository.ItemTypeRepository; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/v1/item_type") +public class ItemTypeController { + + private final ItemTypeRepository repo; + + public ItemTypeController(ItemTypeRepository repo) { + this.repo = repo; + } + + @GetMapping + public List getAll() { + return repo.findAll(); + } + + @PostMapping + public ItemType create(@RequestBody ItemType item) { + return repo.save(item); + } + + @PostMapping("/{id}/update") + public ItemType update(@RequestParam Long id, @RequestBody ItemType item) { + if (id == null) { + throw new RuntimeException("ID is required for update"); + } + + ItemType current = repo.findById(id) + .orElseThrow(() -> new RuntimeException("Item not found")); + + if (current == null) { + throw new RuntimeException("Item not found"); + } + + current.setCode(item.getCode()); + current.setName(item.getName()); + current.setUnit(item.getUnit()); + current.setDescription(item.getDescription()); + + return repo.save(item); + } + + @DeleteMapping("/{id}/delete") + public void delete(@PathVariable Long id) { + repo.deleteById(id); + } +} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/controller/v1/UnitController.java b/backend/src/main/java/xyz/mcorangehq/mcinv/controller/v1/UnitController.java new file mode 100644 index 0000000..9f346b6 --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/controller/v1/UnitController.java @@ -0,0 +1,28 @@ +package xyz.mcorangehq.mcinv.controller.v1; + +import xyz.mcorangehq.mcinv.model.Unit; +import xyz.mcorangehq.mcinv.repository.UnitRepository; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/v1/unit") +public class UnitController { + + private final UnitRepository repo; + + public UnitController(UnitRepository repo) { + this.repo = repo; + } + + @GetMapping + public List getAll() { + return repo.findAll(); + } + + @PostMapping + public Unit create(@RequestBody Unit item) { + return repo.save(item); + } +} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/controller/v1/UserController.java b/backend/src/main/java/xyz/mcorangehq/mcinv/controller/v1/UserController.java new file mode 100644 index 0000000..032863c --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/controller/v1/UserController.java @@ -0,0 +1,28 @@ +package xyz.mcorangehq.mcinv.controller.v1; + +import xyz.mcorangehq.mcinv.model.User; +import xyz.mcorangehq.mcinv.repository.UserRepository; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/v1/user") +public class UserController { + + private final UserRepository repo; + + public UserController(UserRepository repo) { + this.repo = repo; + } + + @GetMapping + public List getAll() { + return repo.findAll(); + } + + @PostMapping + public User create(@RequestBody User item) { + return repo.save(item); + } +} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/controller/v1/WarehouseController.java b/backend/src/main/java/xyz/mcorangehq/mcinv/controller/v1/WarehouseController.java new file mode 100644 index 0000000..41effee --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/controller/v1/WarehouseController.java @@ -0,0 +1,28 @@ +package xyz.mcorangehq.mcinv.controller.v1; + +import xyz.mcorangehq.mcinv.model.Warehouse; +import xyz.mcorangehq.mcinv.repository.WarehouseRepository; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/v1/warehouse") +public class WarehouseController { + + private final WarehouseRepository repo; + + public WarehouseController(WarehouseRepository repo) { + this.repo = repo; + } + + @GetMapping + public List getAll() { + return repo.findAll(); + } + + @PostMapping + public Warehouse create(@RequestBody Warehouse item) { + return repo.save(item); + } +} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/model/ItemMovementLog.java b/backend/src/main/java/xyz/mcorangehq/mcinv/model/ItemMovementLog.java new file mode 100644 index 0000000..a8fc27e --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/model/ItemMovementLog.java @@ -0,0 +1,75 @@ +package xyz.mcorangehq.mcinv.model; + +import java.time.OffsetDateTime; + +import jakarta.persistence.*; + +@Entity +public class ItemMovementLog { + + @Id + @Column(nullable = false) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(nullable = false) + private Warehouse origin_warehouse; + + @ManyToOne + @JoinColumn(nullable = false) + private Warehouse destination_warehouse; + + @ManyToOne + @JoinColumn(nullable = false) + private User user; + + @Column(nullable = false) + private OffsetDateTime timestamp; + + @ManyToOne + @JoinColumn(nullable = false) + private ItemType item; + + @Column(nullable = false) + private double count; + + public ItemMovementLog() {} + + public ItemMovementLog(Warehouse origin, Warehouse destination, ItemType item, User user, double count) { + this.origin_warehouse = origin; + this.destination_warehouse = destination; + this.item = item; + this.user = user; + this.count = count; + this.timestamp = OffsetDateTime.now(); + } + + public Long getId() { + return id; + } + + public Warehouse getDestination() { + return destination_warehouse; + } + + public Warehouse getOrigin() { + return origin_warehouse; + } + + public ItemType getItem() { + return item; + } + + public User getUser() { + return user; + } + + public OffsetDateTime getTimestamp() { + return timestamp; + } + + public double getCount() { + return count; + } +} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/model/ItemType.java b/backend/src/main/java/xyz/mcorangehq/mcinv/model/ItemType.java new file mode 100644 index 0000000..f420605 --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/model/ItemType.java @@ -0,0 +1,67 @@ +package xyz.mcorangehq.mcinv.model; + +import jakarta.persistence.*; + +@Entity +public class ItemType { + + @Id + @Column(nullable = false) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String code; + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private String description; + + @ManyToOne + @JoinColumn(nullable = false) + private Unit unit; + + public ItemType() {} + + public ItemType(String code, String name, String description, Unit unit) { + this.name = name; + this.code = code; + this.description = description; + this.unit = unit; + } + + public Long getId() { + return id; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } + + public String getName() { + return name; + } + + public Unit getUnit() { + return unit; + } + + public void setName(String name) { + this.name = name; + } + public void setCode(String code) { + this.code = code; + } + public void setDescription(String description) { + this.description = description; + } + public void setUnit(Unit unit) { + this.unit = unit; + } +} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/model/Unit.java b/backend/src/main/java/xyz/mcorangehq/mcinv/model/Unit.java new file mode 100644 index 0000000..d1a442f --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/model/Unit.java @@ -0,0 +1,55 @@ +package xyz.mcorangehq.mcinv.model; + +import jakarta.persistence.*; + + +@Entity +public class Unit { + public enum UnitType { + COUNTED, + MEASURED; + + public boolean shouldTruncate() { + return this == COUNTED; + } + } + + @Id + @Column(nullable = false) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private UnitType type; + + public Unit() {} + + public Unit(String name, UnitType type) { + this.name = name; + this.type = type; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public UnitType getType() { + return type; + } + + public void setName(String name) { + this.name = name; + } + + public void setType(UnitType type) { + this.type = type; + } +} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/model/User.java b/backend/src/main/java/xyz/mcorangehq/mcinv/model/User.java new file mode 100644 index 0000000..118b469 --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/model/User.java @@ -0,0 +1,95 @@ +package xyz.mcorangehq.mcinv.model; + +import jakarta.persistence.*; + +@Entity +@Table(name = "users") +public class User { + + @Id + @Column(nullable = false) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String username; + + @Column(nullable = false) + private String first_name = ""; + + @Column(nullable = false) + private String last_name = ""; + + @Column(nullable = false) + private String email = ""; + + @Column(nullable = false) + private String password_hash; + + @Column(nullable = false) + private String password_salt = "" ; + + public User() {} + + public User(String username, String first_name, String last_name, String email, String password_hash, String password_salt) { + this.username = username; + this.first_name = first_name; + this.last_name = last_name; + this.email = email; + this.password_hash = password_hash; + this.password_salt = password_salt; + } + + public Long getId() { + return id; + } + + public String getUsername() { + return username; + } + + public String getFirstName() { + return first_name; + } + + public String getLastName() { + return last_name; + } + + public String getEmail() { + return email; + } + + public String getPasswordHash() { + return password_hash; + } + + public String getPasswordSalt() { + return password_salt; + } + + + public void setUsername(String username) { + this.username = username; + } + + public void setFirstName(String first_name) { + this.first_name = first_name; + } + + public void setLastName(String last_name) { + this.last_name = last_name; + } + + public void setEmail(String email) { + this.email = email; + } + + public void setPasswordHash(String password_hash) { + this.password_hash = password_hash; + } + + public void setPasswordSalt(String password_salt) { + this.password_salt = password_salt; + } +} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/model/UserToken.java b/backend/src/main/java/xyz/mcorangehq/mcinv/model/UserToken.java new file mode 100644 index 0000000..09a07e4 --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/model/UserToken.java @@ -0,0 +1,48 @@ +package xyz.mcorangehq.mcinv.model; + +import java.time.OffsetDateTime; + +import jakarta.persistence.*; + + +@Entity +public class UserToken { + @Id + @Column(nullable = false) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String token; + + @Column(nullable = false) + private OffsetDateTime expires_at; + + @ManyToOne + @JoinColumn(nullable = false) + private User user; + + public UserToken() {} + + public UserToken(User user, String token, OffsetDateTime expires_at) { + this.user = user; + this.token = token; + this.expires_at = expires_at; + } + + public Long getId() { + return id; + } + + public User getUser() { + return user; + } + + public String getToken() { + return token; + } + + public OffsetDateTime getExpiresAt() { + return expires_at; + } +} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/model/Warehouse.java b/backend/src/main/java/xyz/mcorangehq/mcinv/model/Warehouse.java new file mode 100644 index 0000000..b880945 --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/model/Warehouse.java @@ -0,0 +1,55 @@ +package xyz.mcorangehq.mcinv.model; + +import jakarta.persistence.*; + +@Entity +public class Warehouse { + + @Id + @Column(nullable = false) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private String description; + + @ManyToOne + @JoinColumn(nullable = false) + private User owner; + + public Warehouse() {} + + public Warehouse(String name, String description) { + this.name = name; + this.description = description; + } + + public Long getId() { + return id; + } + + public User getOwner() { + return owner; + } + + public String getDescription() { + return description; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + public void setOwner(User owner) { + this.owner = owner; + } + public void setDescription(String description) { + this.description = description; + } +} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/model/WarehouseItem.java b/backend/src/main/java/xyz/mcorangehq/mcinv/model/WarehouseItem.java new file mode 100644 index 0000000..c03a963 --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/model/WarehouseItem.java @@ -0,0 +1,67 @@ +package xyz.mcorangehq.mcinv.model; + +import jakarta.persistence.*; + +@Entity +public class WarehouseItem { + + @Id + @Column(nullable = false) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(nullable = false) + private Warehouse warehouse; + + @ManyToOne + @JoinColumn(nullable = false) + private ItemType item; + + @Column(nullable = false) + private double count; + + public WarehouseItem() {} + + public WarehouseItem(Warehouse wh, ItemType item, double count) { + this.warehouse = wh; + this.item = item; + this.count = count; + } + + public Long getId() { + return id; + } + + public Warehouse getWarehouse() { + return warehouse; + } + + public ItemType getItem() { + return item; + } + + public double getCount() { + return count; + } + + public void add(double ammount) { + if (item.getUnit().getType().shouldTruncate()) { + // truncate to whole number if counted not measured + this.count += (double)(long)ammount; + } else { + this.count += ammount; + } + } + public void remove(double ammount) { + if (item.getUnit().getType().shouldTruncate()) { + // truncate to whole number if counted not measured + this.count -= (double)(long)ammount; + } else { + this.count -= ammount; + } + } + public boolean isNegativeStock() { + return this.count < 0; + } +} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/model/dto/LoginRequest.java b/backend/src/main/java/xyz/mcorangehq/mcinv/model/dto/LoginRequest.java new file mode 100644 index 0000000..b20d162 --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/model/dto/LoginRequest.java @@ -0,0 +1,25 @@ +package xyz.mcorangehq.mcinv.model.dto; + +public class LoginRequest { + + private String username; + private String password; + + public LoginRequest() {} + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setUsername(String username) { + this.username = username; + } +} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/repository/ItemMovementLogRepository.java b/backend/src/main/java/xyz/mcorangehq/mcinv/repository/ItemMovementLogRepository.java new file mode 100644 index 0000000..b51c314 --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/repository/ItemMovementLogRepository.java @@ -0,0 +1,6 @@ +package xyz.mcorangehq.mcinv.repository; + +import xyz.mcorangehq.mcinv.model.ItemMovementLog; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ItemMovementLogRepository extends JpaRepository {} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/repository/ItemTypeRepository.java b/backend/src/main/java/xyz/mcorangehq/mcinv/repository/ItemTypeRepository.java new file mode 100644 index 0000000..ab0c2c3 --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/repository/ItemTypeRepository.java @@ -0,0 +1,6 @@ +package xyz.mcorangehq.mcinv.repository; + +import xyz.mcorangehq.mcinv.model.ItemType; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ItemTypeRepository extends JpaRepository {} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/repository/UnitRepository.java b/backend/src/main/java/xyz/mcorangehq/mcinv/repository/UnitRepository.java new file mode 100644 index 0000000..c7e10fb --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/repository/UnitRepository.java @@ -0,0 +1,6 @@ +package xyz.mcorangehq.mcinv.repository; + +import xyz.mcorangehq.mcinv.model.Unit; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UnitRepository extends JpaRepository {} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/repository/UserRepository.java b/backend/src/main/java/xyz/mcorangehq/mcinv/repository/UserRepository.java new file mode 100644 index 0000000..8238797 --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/repository/UserRepository.java @@ -0,0 +1,8 @@ +package xyz.mcorangehq.mcinv.repository; + +import xyz.mcorangehq.mcinv.model.User; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository { + User findByUsername(String username); +} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/repository/UserTokenRepository.java b/backend/src/main/java/xyz/mcorangehq/mcinv/repository/UserTokenRepository.java new file mode 100644 index 0000000..eae44e8 --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/repository/UserTokenRepository.java @@ -0,0 +1,7 @@ +package xyz.mcorangehq.mcinv.repository; + +import xyz.mcorangehq.mcinv.model.UserToken; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserTokenRepository extends JpaRepository { +} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/repository/WarehouseItemRepository.java b/backend/src/main/java/xyz/mcorangehq/mcinv/repository/WarehouseItemRepository.java new file mode 100644 index 0000000..21aa744 --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/repository/WarehouseItemRepository.java @@ -0,0 +1,6 @@ +package xyz.mcorangehq.mcinv.repository; + +import xyz.mcorangehq.mcinv.model.WarehouseItem; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface WarehouseItemRepository extends JpaRepository {} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/repository/WarehouseRepository.java b/backend/src/main/java/xyz/mcorangehq/mcinv/repository/WarehouseRepository.java new file mode 100644 index 0000000..9b7e113 --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/repository/WarehouseRepository.java @@ -0,0 +1,6 @@ +package xyz.mcorangehq.mcinv.repository; + +import xyz.mcorangehq.mcinv.model.Warehouse; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface WarehouseRepository extends JpaRepository {} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/security/JwtFilter.java b/backend/src/main/java/xyz/mcorangehq/mcinv/security/JwtFilter.java new file mode 100644 index 0000000..f740b00 --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/security/JwtFilter.java @@ -0,0 +1,48 @@ +package xyz.mcorangehq.mcinv.security; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +public class JwtFilter extends OncePerRequestFilter { + + private final JwtService jwtService; + + public JwtFilter(JwtService jwtService) { + this.jwtService = jwtService; + } + + @Override + protected void doFilterInternal( + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain + ) throws ServletException, IOException { + + String header = request.getHeader("Authorization"); + + if (header == null || !header.startsWith("Bearer ")) { + filterChain.doFilter(request, response); + return; + } + + String token = header.substring(7); + + try { + String userId = jwtService.validateAndGetSubject(token); + + // later we can store user in context + request.setAttribute("userId", userId); + + } catch (Exception e) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return; + } + + filterChain.doFilter(request, response); + } +} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/security/JwtService.java b/backend/src/main/java/xyz/mcorangehq/mcinv/security/JwtService.java new file mode 100644 index 0000000..2bf417c --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/security/JwtService.java @@ -0,0 +1,41 @@ +package xyz.mcorangehq.mcinv.security; + +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.util.Date; + +import org.springframework.stereotype.Service; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.Claims; +import xyz.mcorangehq.mcinv.model.User; + +@Service +public class JwtService { + private final Key key = Keys.hmacShaKeyFor( + "your-super-long-secret-key-that-is-at-least-32-bytes".getBytes(StandardCharsets.UTF_8) + ); + + public String generateToken(User user) { + return Jwts.builder() + .setSubject(user.getId().toString()) + .claim("username", user.getUsername()) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + 86400000)) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + } + + public String validateAndGetSubject(String token) { + + Claims claims = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody(); + + return claims.getSubject(); + } +} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/service/AuthService.java b/backend/src/main/java/xyz/mcorangehq/mcinv/service/AuthService.java new file mode 100644 index 0000000..6a4b1b2 --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/service/AuthService.java @@ -0,0 +1,37 @@ +package xyz.mcorangehq.mcinv.service; + +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import xyz.mcorangehq.mcinv.model.User; +import xyz.mcorangehq.mcinv.repository.UserRepository; +import xyz.mcorangehq.mcinv.security.JwtService; + +@Service +public class AuthService { + + private final UserRepository repo; + private final PasswordEncoder encoder; + private final JwtService jwtService; + + public AuthService(UserRepository repo, PasswordEncoder encoder, JwtService jwtService) { + this.repo = repo; + this.encoder = encoder; + this.jwtService = jwtService; + } + + public String login(String username, String password) { + + User user = repo.findByUsername(username); + + if (user == null) { + throw new RuntimeException("Invalid login"); + } + + if (!encoder.matches(password, user.getPasswordHash())) { + throw new RuntimeException("Invalid login"); + } + + return jwtService.generateToken(user); + } +} diff --git a/backend/src/main/java/xyz/mcorangehq/mcinv/service/UserService.java b/backend/src/main/java/xyz/mcorangehq/mcinv/service/UserService.java new file mode 100644 index 0000000..ac3313c --- /dev/null +++ b/backend/src/main/java/xyz/mcorangehq/mcinv/service/UserService.java @@ -0,0 +1,29 @@ +package xyz.mcorangehq.mcinv.service; + +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import xyz.mcorangehq.mcinv.model.User; +import xyz.mcorangehq.mcinv.repository.UserRepository; + +@Service +public class UserService { + + private final PasswordEncoder encoder; + private UserRepository userRepository; + + public UserService(PasswordEncoder encoder, UserRepository userRepository) { + this.encoder = encoder; + this.userRepository = userRepository; + } + + public User createUser(String username, String password) { + User u = new User(); + u.setUsername(username); + + String hashed = encoder.encode(password); + u.setPasswordHash(hashed); + + return userRepository.save(u); + } +} diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index dab7093..c28efb0 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -1 +1,9 @@ spring.application.name=mcinv + +spring.datasource.url=jdbc:postgresql://localhost:5432/mcinv +spring.datasource.username=mcinv +spring.datasource.password=testing_password + +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true +spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect diff --git a/frontend/api/client.php b/frontend/api/client.php new file mode 100644 index 0000000..46be2e3 --- /dev/null +++ b/frontend/api/client.php @@ -0,0 +1,35 @@ + + + + + + Items + + + + + + + Items + + + + + ID + Name + Unit Type + + + + + + = htmlspecialchars($item["id"]) ?> + = htmlspecialchars($item["name"]) ?> + = htmlspecialchars($item["unitType"]) ?> + + + + + + + + + + diff --git a/frontend/login.php b/frontend/login.php new file mode 100644 index 0000000..154e658 --- /dev/null +++ b/frontend/login.php @@ -0,0 +1,79 @@ + $_POST["username"], + "password" => $_POST["password"] + ] + ); + + if ($response) { + + setcookie( + "token", + $response, + time() + 86400, + "/" + ); + + header("Location: items.php"); + exit; + + } else { + $error = "Invalid login"; + } +} +?> + + + + + Login + + + + + + + Login + + + = $error ?> + + + + + + + + + + Login + + + + + + + + diff --git a/frontend/warehouses.php b/frontend/warehouses.php new file mode 100644 index 0000000..e69de29
= $error ?>