Initial setup, not working yet

This commit is contained in:
2026-06-12 15:08:50 +03:00
parent 2aba4b03a4
commit a467754d6c
37 changed files with 1211 additions and 4 deletions

View File

@@ -30,16 +30,48 @@
<java.version>17</java.version> <java.version>17</java.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency> <!-- core spring runtime -->
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId> <artifactId>spring-boot-starter</artifactId>
</dependency> </dependency>
<dependency> <!-- spring web framework -->
<dependency> <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> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <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> </dependencies>
<build> <build>

View File

@@ -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();
}
}

View File

@@ -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");
};
}
}

View File

@@ -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());
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View 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;
}
}

View 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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> {}

View File

@@ -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> {}

View File

@@ -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> {}

View File

@@ -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);
}

View File

@@ -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> {
}

View File

@@ -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> {}

View File

@@ -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> {}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -1 +1,9 @@
spring.application.name=mcinv 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
View 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
View File

@@ -0,0 +1,3 @@
<?php
define("API_BASE", "http://localhost:8080");

24
frontend/assets/style.css Normal file
View 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
View File

11
frontend/index.php Normal file
View 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
View 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
View 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
View File