Initial setup, not working yet
This commit is contained in:
@@ -30,16 +30,48 @@
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<dependency> <!-- core spring runtime -->
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<dependency> <!-- spring web framework -->
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency> <!-- sprint database adapter -->
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency> <!-- for password hashing -->
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency> <!-- psql driver -->
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
</dependency>
|
||||
<dependency> <!-- sprint testing framework -->
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</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>
|
||||
<version>0.11.5</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>0.11.5</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<ItemMovementLog> getAll() {
|
||||
return repo.findAll();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<ItemType> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<Unit> getAll() {
|
||||
return repo.findAll();
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public Unit create(@RequestBody Unit item) {
|
||||
return repo.save(item);
|
||||
}
|
||||
}
|
||||
@@ -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<User> getAll() {
|
||||
return repo.findAll();
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public User create(@RequestBody User item) {
|
||||
return repo.save(item);
|
||||
}
|
||||
}
|
||||
@@ -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<Warehouse> getAll() {
|
||||
return repo.findAll();
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public Warehouse create(@RequestBody Warehouse item) {
|
||||
return repo.save(item);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
55
backend/src/main/java/xyz/mcorangehq/mcinv/model/Unit.java
Normal file
55
backend/src/main/java/xyz/mcorangehq/mcinv/model/Unit.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
95
backend/src/main/java/xyz/mcorangehq/mcinv/model/User.java
Normal file
95
backend/src/main/java/xyz/mcorangehq/mcinv/model/User.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<ItemMovementLog, Long> {}
|
||||
@@ -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<ItemType, Long> {}
|
||||
@@ -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<Unit, Long> {}
|
||||
@@ -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, Long> {
|
||||
User findByUsername(String username);
|
||||
}
|
||||
@@ -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<UserToken, Long> {
|
||||
}
|
||||
@@ -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<WarehouseItem, Long> {}
|
||||
@@ -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<Warehouse, Long> {}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
35
frontend/api/client.php
Normal file
35
frontend/api/client.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . "/config.php";
|
||||
|
||||
function api_request($method, $endpoint, $data = null, $token = null) {
|
||||
|
||||
$ch = curl_init(API_BASE . $endpoint);
|
||||
|
||||
$headers = [
|
||||
"Content-Type: application/json"
|
||||
];
|
||||
|
||||
if ($token) {
|
||||
$headers[] = "Authorization: Bearer " . $token;
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
|
||||
|
||||
if ($data !== null) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
|
||||
if ($response === false) {
|
||||
die(curl_error($ch));
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
return json_decode($response, true);
|
||||
}
|
||||
3
frontend/api/config.php
Normal file
3
frontend/api/config.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
define("API_BASE", "http://localhost:8080");
|
||||
24
frontend/assets/style.css
Normal file
24
frontend/assets/style.css
Normal file
@@ -0,0 +1,24 @@
|
||||
body {
|
||||
background: #111;
|
||||
color: #eee;
|
||||
font-family: sans-serif;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
input, button {
|
||||
padding: 0.5rem;
|
||||
margin: 0.3rem 0;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
td, th {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
0
frontend/dashboard.php
Normal file
0
frontend/dashboard.php
Normal file
11
frontend/index.php
Normal file
11
frontend/index.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
$token = $_COOKIE["token"] ?? null;
|
||||
|
||||
if ($token) {
|
||||
header("Location: items.php");
|
||||
} else {
|
||||
header("Location: login.php");
|
||||
}
|
||||
|
||||
exit;
|
||||
59
frontend/items.php
Normal file
59
frontend/items.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
require_once "api/client.php";
|
||||
|
||||
$token = $_COOKIE["token"] ?? null;
|
||||
|
||||
if (!$token) {
|
||||
header("Location: login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$items = api_request(
|
||||
"GET",
|
||||
"/item",
|
||||
null,
|
||||
$token
|
||||
);
|
||||
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Items</title>
|
||||
<link rel="stylesheet" href="assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<h1>Items</h1>
|
||||
|
||||
<table border="1" cellpadding="8">
|
||||
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Unit Type</th>
|
||||
</tr>
|
||||
|
||||
<?php foreach ($items as $item): ?>
|
||||
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($item["id"]) ?></td>
|
||||
<td><?= htmlspecialchars($item["name"]) ?></td>
|
||||
<td><?= htmlspecialchars($item["unitType"]) ?></td>
|
||||
</tr>
|
||||
|
||||
<?php endforeach; ?>
|
||||
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
79
frontend/login.php
Normal file
79
frontend/login.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
require_once "api/client.php";
|
||||
|
||||
$error = null;
|
||||
|
||||
if ($_POST) {
|
||||
|
||||
$response = api_request(
|
||||
"POST",
|
||||
"/auth/login",
|
||||
[
|
||||
"username" => $_POST["username"],
|
||||
"password" => $_POST["password"]
|
||||
]
|
||||
);
|
||||
|
||||
if ($response) {
|
||||
|
||||
setcookie(
|
||||
"token",
|
||||
$response,
|
||||
time() + 86400,
|
||||
"/"
|
||||
);
|
||||
|
||||
header("Location: items.php");
|
||||
exit;
|
||||
|
||||
} else {
|
||||
$error = "Invalid login";
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Login</title>
|
||||
<link rel="stylesheet" href="assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<h1>Login</h1>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<p><?= $error ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST">
|
||||
|
||||
<input
|
||||
name="username"
|
||||
placeholder="Username"
|
||||
required
|
||||
>
|
||||
|
||||
<input
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
required
|
||||
>
|
||||
|
||||
<button type="submit">
|
||||
Login
|
||||
</button>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
0
frontend/warehouses.php
Normal file
0
frontend/warehouses.php
Normal file
Reference in New Issue
Block a user