Senin 18 November 2019

This commit is contained in:
Damillora 2019-11-18 20:33:45 +07:00
parent 4d29a48f79
commit 7803ea548d
36 changed files with 55502 additions and 145 deletions

6
.htaccess Normal file
View File

@ -0,0 +1,6 @@
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /index.php/$1 [L]
</IfModule>

View File

@ -0,0 +1,133 @@
<?php
namespace Application\Controllers;
use Application\HTTP\Request;
use Application\HTTP\Response;
use Application\Services\ServiceContainer;
use Application\Foundations\QueryBuilder;
use Application\Foundations\MailBuilder;
use Application\Models\User;
use Application\Models\UserConfirmation;
class AuthController {
public function __construct() {
}
public function sign_up(Request $request, Response $response) {
if(ServiceContainer::Authentication()->isLoggedIn()) {
return $response->redirect('/');
}
return $response->view('sign-up');
}
public function create_user(Request $request, Response $response) {
if($request->email == "") {
return $response->redirect("/signup")->with(
[ 'errors' => 'Email must not be empty' ]
);
} else if (!filter_var($request->email,FILTER_VALIDATE_EMAIL)) {
return $response->redirect("/signup")->with(
[ 'errors' => 'Email must not valid' ]
);
} else if ($request->username == "") {
return $response->redirect("/signup")->with(
[ 'errors' => 'Username must not be empty' ]
);
} else if (strlen($request->username) < 6 || strlen($request->username) > 20 ) {
return $response->redirect("/signup")->with(
[ 'errors' => 'Username must be between 6 and 20 characters' ]
);
} else if (strlen($request->password) < 8) {
return $response->redirect("/signup")->with(
[ 'errors' => 'Password must be at least 8 characters' ]
);
} else if ($request->password != $request->confirmpassword) {
return $response->redirect("/signup")->with(
[ 'errors' => 'Password and confirm password must be the same' ]
);
}
ServiceContainer::Session()->unset('errors');
$data = User::create([
'id' => null,
'username' => $request->username,
'about' => '',
'email' => $request->email,
'email_visible' => 0,
'avatar_path' => '',
'password' => password_hash($request->password,PASSWORD_DEFAULT),
'is_confirmed' => 0,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
if($data != null) {
$id = $data->id;
$confirmator = UserConfirmation::create([
'confirm_key' => hash('sha256',$data->username.time()),
'user_id' => $id,
'best_before' => date('Y-m-d H:i:s', strtotime('+6 hours', time())),
]);
$email = new MailBuilder();
$body = "Thank you for registering with metaforums.\n";
$body .= "To be able to explore the vast forum, use the URL below:\n\n";
$body .= $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['SERVER_NAME'].'/signup/confirm?'.$confirmator->confirm_key;
$email->from("metaforums@nanao.moe")->to($data->email)->subject("Complete your registration on Metaforums")->body($body);
ServiceContainer::Email()->send($email);
if($confirmator != null) {
return $response->redirect('/signup/success')->with(
[ 'signup-email' => $request->email ],
);
}
}
}
public function sign_up_success(Request $request, Response $response) {
return $response->view('sign-up-success');
}
public function sign_up_confirm(Request $request, Response $response) {
$confirm = UserConfirmation::find($request->queryString());
if(isset($confirm)) {
$id = $confirm->user_id;
$user = User::find($id);
$user->update([ 'is_confirmed' => 1 ]);
$confirm->delete();
}
return $response->redirect('/');
}
public function login(Request $request, Response $response) {
if(ServiceContainer::Authentication()->isLoggedIn()) {
return $response->redirect('/');
}
return $response->view('login');
}
public function login_check(Request $request, Response $response) {
if ($request->username == "") {
return $response->redirect("/login")->with(
[ 'errors' => 'Username must not be empty' ]
);
} else if ($request->password == "") {
return $response->redirect("/login")->with(
[ 'errors' => 'Password must not be empty' ]
);
}
$query = new QueryBuilder();
$query = $query->select('id,password')->from('user')->where('username',$request->username)->where('is_confirmed',1)->build();
$result = ServiceContainer::Database()->select($query);
if(count($result) == 0) {
return $response->redirect("/login")->with(
[ 'errors' => 'Wrong username or password' ]
);
} else {
$password = $result[0]["password"];
$verify = password_verify($request->password,$password);
if(!$verify) {
return $response->redirect("/login")->with(
[ 'errors' => 'Wrong username or password' ]
);
}
}
ServiceContainer::Session()->set('user_id',$result[0]['id']);
return $response->redirect('/');
}
public function logout(Request $request, Response $response) {
ServiceContainer::Session()->destroy();
return $response->redirect('/login');
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace Application\Controllers;
use Application\HTTP\Request;
use Application\HTTP\Response;
use Application\Services\ServiceContainer;
class IndexController {
public function __construct() {
}
public function index(Request $request, Response $response) {
return $response->view('index');
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Application\Foundations;
class MailBuilder {
public $to = "";
public $subject = "";
public $message = "";
public $headers = "";
public function to($to) {
$this->to = $to;
return $this;
}
public function subject($subject) {
$this->subject = $subject;
return $this;
}
public function body($body) {
$this->message = $body;
return $this;
}
public function from($from) {
$this->headers .= "From: ".$from."\r\n";
return $this;
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace Application\Foundations;
use Application\Services\ServiceContainer;
class Model {
public $attributes;
protected $primary_key = 'id';
public function __construct() {
}
public function hydrate($data) {
$this->attributes = $data;
}
public static function create($data) {
$calling_class = get_called_class();
$class = explode('\\',get_called_class());
$tablename = strtolower($class[count($class)-1]);
$result = ServiceContainer::Database()->insert($tablename, $data);
if($result) {
$data['id'] = $result;
}
$inst = new $calling_class();
$inst->hydrate($result);
return $inst;
}
public static function find($key) {
$calling_class = get_called_class();
$inst = new $calling_class();
$class = explode('\\',get_called_class());
$tablename = strtolower($class[count($class)-1]);
$query = new QueryBuilder();
$query = $query->select('*')->from($tablename)->where($inst->primary_key,$key)->build();
$result = ServiceContainer::Database()->select($query);
if(count($result) == 0) return null;
$inst->hydrate($result[0]);
return $inst;
}
public function update($key) {
$calling_class = get_called_class();
$class = explode('\\',get_called_class());
$tablename = strtolower($class[count($class)-1]);
$query = new QueryBuilder();
$query = $query->update($tablename)->set($key)->where($this->primary_key,$this->attributes[$this->primary_key])->build();
$result = ServiceContainer::Database()->update($query);
if(!$result) return null;
else {
return $this;
}
}
public function delete() {
$calling_class = get_called_class();
$class = explode('\\',get_called_class());
$tablename = strtolower($class[count($class)-1]);
$query = new QueryBuilder();
$query = $query->delete()->from($tablename)->where($this->primary_key,$this->attributes[$this->primary_key])->build();
$result = ServiceContainer::Database()->update($query);
if(!$result) return $this;
else {
return null;
}
}
function __get($prop) {
return $this->attributes[$prop];
}
function __set($prop, $val) {
$this->attributes[$prop] = $val;
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace Application\Foundations;
class QueryBuilder {
private $query = "";
private $where = "";
public function select($fields) {
if(!is_array($fields)) {
$this->query .= "SELECT ".$fields;
} else {
$this->query .= "SELECT ".implode(",",SQLHelper::encode_list($fields));
}
return $this;
}
public function delete() {
$this->query .= "DELETE";
return $this;
}
public function update($table) {
// TODO: SQL injection
$this->query .= "UPDATE ".$table;
return $this;
}
public function set($data) {
$this->query .= " SET";
$final = [];
foreach($data as $key => $value) {
$final[] = $key." = ".SQLHelper::encode_literal($value);
}
$this->query .= " ".implode(",",$final);
return $this;
}
public function from($table) {
// TODO: SQL injection
$this->query .= " FROM ".$table;
return $this;
}
public function where($a, $b, $c = null) {
$field = "";
$value = "";
$operator = "=";
if($c == null) {
// 2 param syntax
$field = $a;
$value = $b;
} else {
$field = $a;
$value = $c;
$operator = $b;
}
$value = SQLHelper::encode_literal($value);
if($this->where == "") {
$this->where .= " WHERE ".$field." ".$operator." ".$value;
} else {
$this->where .= " AND ".$field." ".$operator." ".$value;
}
return $this;
}
public function orWhere($a, $b, $c = null) {
$field = "";
$value = "";
$operator = "=";
if($c == null) {
// 2 param syntax
$field = $a;
$value = $b;
} else {
$field = $a;
$value = $c;
$operator = $b;
}
if($this->where == "") {
$this->where .= " WHERE ".$field." ".$operator." ".$value;
} else {
$this->where .= " OR ".$field." ".$operator." ".$value;
}
return $this;
}
public function build() {
return $this->query.$this->where;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Application\Foundations;
use Application\Services\ServiceContainer;
class SQLHelper {
public static function encode_list($data) {
$insert_data = $data;
foreach($insert_data as $index => $val) {
$insert_data[$index] = SQLHelper::encode_literal($val);
}
return $insert_data;
}
public static function encode_literal($val) {
$db = ServiceContainer::Database();
if(is_numeric($val)) {
return $val;
} else if(is_null($val)) {
return 'NULL';
} else if(!is_numeric($val)) {
return '"'.$db->escapeString($val).'"';
} else if($val == "") {
return '""';
} else {
return $val;
}
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Application\HTTP;
class Request {
private $data;
private $query;
public function __construct() {
$this->data = $_REQUEST;
$this->query = $_SERVER['QUERY_STRING'];
}
function queryString() {
return $this->query;
}
function __get($prop) {
return $this->data[$prop];
}
function __set($prop, $val) {
$this->data[$prop] = $val;
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace Application\HTTP;
use Application\Services\ServiceContainer;
class Response {
private $body = "";
private $status = 200;
private $headers = [];
public function view($path, $args = []) {
$this->body .= ServiceContainer::View()->render($path,$args);
return $this;
}
public function data($data) {
return $this->json()->body(json_encode($data));
}
public function body($body) {
$this->body = $body;
return $this;
}
public function statusCode($status) {
$this->status = $status;
return $this;
}
public function header($head) {
$this->headers[] = $head;
return $this;
}
public function json() {
return $this->header('Content-Type: application/json');
}
public function render() {
http_response_code($this->status);
foreach($this->headers as $header) {
header($header);
}
echo $this->body;
}
public function redirect($path) {
return $this->header('Location: '.$path);
}
public function with($data) {
foreach($data as $key => $val) {
ServiceContainer::Session()->set($key,$val);
}
return $this;
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Application\Models;
use Application\Foundations\Model as DBModel;
class User extends DBModel {
}

View File

@ -0,0 +1,8 @@
<?php
namespace Application\Models;
use Application\Foundations\Model as DBModel;
class UserConfirmation extends DBModel {
protected $primary_key = 'confirm_key';
}

View File

@ -0,0 +1,11 @@
<?php
namespace Application\Services;
class Authentication {
public function __construct() {
ServiceContainer::Session();
}
public function isLoggedIn() {
return ServiceContainer::Session()->has('user_id');
}
}

View File

@ -1,10 +1,10 @@
<?php <?php
namespace Mitsumine\Services; namespace Application\Services;
class Config { class Config {
private $configs; private $configs;
public function __construct() { public function __construct() {
$this->configs = require 'backend/config.php'; $this->configs = require 'config.php';
} }
public function __call($name, $args) { public function __call($name, $args) {
return $this->configs[$name]; return $this->configs[$name];

View File

@ -0,0 +1,43 @@
<?php
namespace Application\Services;
use Application\Services\ServiceContainer;
use Application\Foundations\SQLHelper;
class Database {
private $conn;
private $config;
public function __construct() {
$this->config = ServiceContainer::Config();
$this->conn = mysqli_connect($this->config->db_host(),$this->config->db_user(),$this->config->db_pass(),$this->config->db_name());
}
public function insert($table, $data) {
$insert_data = SQLHelper::encode_list($data);
$key_names = array_keys($insert_data);
$query = "INSERT INTO ".$table." (".implode(",",$key_names).") VALUES (".implode(",",$insert_data).")";
$result = mysqli_query($this->conn,$query);
if($result) {
return mysqli_insert_id($this->conn);
} else {
echo mysqli_error($this->conn);
return null;
}
}
public function update($query) {
$result = mysqli_query($this->conn,$query);
return $result;
}
public function select($query) {
$result = mysqli_query($this->conn,$query);
if($result) {
return mysqli_fetch_all($result,MYSQLI_ASSOC);
} else {
return null;
}
}
// Escaping strings requires DB connection, which is only handled by the Database service.
public function escapeString($str) {
return mysqli_real_escape_string($this->conn,$str);
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Application\Services;
use Application\Foundations\MailBuilder;
class Email {
public static function send($email) {
mail($email->to,$email->subject,$email->message,$email->headers);
}
}

View File

@ -1,5 +1,5 @@
<?php <?php
namespace Mitsumine\Services; namespace Application\Services;
class ServiceContainer{ class ServiceContainer{
private static $services = []; private static $services = [];
@ -10,7 +10,7 @@ class ServiceContainer{
return self::$services[$service]; return self::$services[$service];
} }
public static function load($service) { public static function load($service) {
$class = 'Mitsumine\\Services\\'.$service; $class = 'Application\\Services\\'.$service;
self::$services[$service] = new $class(); self::$services[$service] = new $class();
} }
public static function __callStatic($name, $args) { public static function __callStatic($name, $args) {

View File

@ -0,0 +1,23 @@
<?php
namespace Application\Services;
class Session {
public function __construct() {
session_start();
}
public function get($path) {
return $_SESSION[$path];
}
public function set($path, $val) {
$_SESSION[$path] = $val;
}
public function unset($path) {
$_SESSION[$path] = null;
}
public function has($path) {
return isset($_SESSION[$path]);
}
public function destroy() {
session_destroy();
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Application\Services;
class View {
public function render($path, $args = []) {
ob_start();
extract($args);
$auth = ServiceContainer::Authentication();
include('Application/Views/'.$path.'.php');
$rendered_string = ob_get_contents();
ob_end_clean();
return $rendered_string;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
<?php
include 'layouts/head.php';
?>
<?php
include 'layouts/foot.php';
?>

View File

@ -0,0 +1,5 @@
</main>
<footer>
</footer>
</body>
</html>

View File

@ -0,0 +1,23 @@
<html>
<head>
<link href="/css/metaforums.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>Metaforums</title>
</head>
<body>
<header>
<div class="header-left">
<a href="/" class="header-link">Metaforums</a>
</div>
<div class="header-middle">
</div>
<div class="header-left">
<?php if($auth->isLoggedIn()) { ?>
<a href="/logout" class="header-link">Logout</a>
<?php } else {?>
<a href="/login" class="header-link">Login</a>
<a href="/signup" class="header-link">Signup</a>
<?php } ?>
</div>
</header>
<main>

View File

@ -0,0 +1,52 @@
<?php
include 'layouts/head.php';
?>
<div id="login">
<form action="/login" method="POST" id="login">
<h1 class="form-title">Login</h1>
<div class="input-group">
<input type="text" name="username" id="username" placeholder="username" v-model="username"></input>
</div>
<div class="input-group">
<input type="password" name="password" id="password" placeholder="password" v-model="password"></input>
</div>
<div id="errors" v-if="errors != ''">
<p>{{ errors }}</p>
</div>
<div class="input-group">
<button type="submit" id="submit" @click="validate">Sign in</button>
</div>
</form>
</div>
<script>
var app = new Vue({
el: "#login",
data: {
username: "",
password: "",
errors: "<?php echo $_SESSION['errors'] ?? "" ?>",
},
methods: {
validate: function(e) {
// https://stackoverflow.com/questions/46155/how-to-validate-an-email-address-in-javascript
var emailre = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
var emailvalid = emailre.test(String(this.email).toLowerCase()) ;
if(this.username == "") {
this.errors = "Username must not be empty";
e.preventDefault();
return false;
} else if(this.password == "") {
this.errors = "Password must not be empty";
e.preventDefault();
return false;
}
this.errors = "";
return true;
}
}
});
</script>
<?php
include 'layouts/foot.php';
?>

View File

@ -0,0 +1,9 @@
<?php
include 'layouts/head.php';
?>
<h1>You are registered!</h1>
<p>However, you will need to confirm your email address before you can start exploring the wonderful world of our forum</p>
<p>Please check your inbox for further instructions</p>
<?php
include 'layouts/foot.php';
?>

View File

@ -0,0 +1,76 @@
<?php
include 'layouts/head.php';
?>
<div id="signup">
<form action="/signup" method="POST" id="signup">
<h1 class="form-title">Sign up</h1>
<div class="input-group">
<input type="text" name="email" id="email" placeholder="email" v-model="email"></input>
</div>
<div class="input-group">
<input type="text" name="username" id="username" placeholder="username" v-model="username"></input>
</div>
<div class="input-group">
<input type="password" name="password" id="password" placeholder="password" v-model="password"></input>
</div>
<div class="input-group">
<input type="password" name="confirmpassword" id="confirmpassword" placeholder="confirm password" v-model="confirmpassword"></input>
</div>
<div id="errors" v-if="errors != ''">
<p>{{ errors }}</p>
</div>
<div class="input-group">
<button type="submit" id="submit" @click="validate">Sign Up</button>
</div>
</form>
</div>
<script>
var app = new Vue({
el: "#signup",
data: {
email: "",
username: "",
password: "",
confirmpassword: "",
errors: "<?php echo $_SESSION['errors'] ?? "" ?>",
},
methods: {
validate: function(e) {
// https://stackoverflow.com/questions/46155/how-to-validate-an-email-address-in-javascript
var emailre = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
var emailvalid = emailre.test(String(this.email).toLowerCase()) ;
if(this.email == "") {
this.errors = "Email must not be empty";
e.preventDefault();
return false;
} else if(!emailvalid) {
this.errors = "Email is invalid";
e.preventDefault();
return false;
} else if(this.username == "") {
this.errors = "Username must not be empty";
e.preventDefault();
return false;
} else if(this.username.length < 6 || this.username.length > 20) {
this.errors = "Username must be between 6 and 20 characters";
e.preventDefault();
return false;
} else if(this.password.length < 8) {
this.errors = "Password must be at least 8 characters";
e.preventDefault();
return false;
} else if(this.password !== this.confirmpassword) {
this.errors = "Password and confirm password must be the same";
e.preventDefault();
return false;
}
this.errors = "";
return true;
}
}
});
</script>
<?php
include 'layouts/foot.php';
?>

View File

@ -1,26 +1,56 @@
# Metaforums # Metaforums
An online web-based discussion forum application An online web-based discussion forum application
## How to set up
1. Make sure mod_rewrite is enabled on Apache.
2. Create a MySQL / MariaDB database, and import Metaforums' schema (`schema.sql`) into the database.
3. Modify backend/config.php and adjust the configuration as needed.
4. Point the browser to `localhost`
## Project Structure ## Project Structure
This project is separated between the frontend and the backend application.
- backend/
The backend of this application is written in PHP, and is API focused.
- index.php - index.php
The main handler of backend functions. Handles routing, loading of Mitsumine services, and conversion of array responses to JSON. The main handler of backend functions. Handles routing, loading of Application services, and response handling.
- Mitsumine/
Mitsumine is a set of custom-written helper classes to consolidate frequently used code.
- Mitsumine/HTTP
Mitsumine HTTP contains a number of abstractions for HTTP, such as Request class
- Mitsumine/Services
Mitsumine Services contains a number of service classes for common functionality such as Database and Session.
- Application/ - Application/
Application contains classes that are the core of the application itself Application contains classes that are the core of the application itself
- Assets/
Assets contains buildable assets, for example source CSS.
- Controllers/ - Controllers/
Controllers contain controllers that return HTTP responses Controllers contain controllers that return HTTP responses
- frontend/ - Foundations/
The frontend of this application, written in HTML and utilizes T Foundations are helper classes for various functions, such as an SQL query builder, and base model implementation;
- index.php - SQLHelper
This index file allows serving both frontend and backend from one endpoint. Contains SQL escaping facilities.
- QueryBuilder
The SQL query builder.
- Model
The base model implementation. Contains common code for all models.
- HTTP
HTTP contains a number of abstractions for HTTP, such as Request class.
- Models/
Models contain database models.
- Services/
Services contains a number of service classes for common functionality such as Database and Session.
- Authentication
Provide auth related services.
- Config
Loads configuration and provides a facility to access the contents.
- Database
A service that centralizes database access.
- Email
A service for sending email.
- ServiceContainer
A service container that ensures every service is loaded once.
- Session
A service that contains centralized session management.
- View
Provides view rendering facilities
- Static/
Static contains static files served directly by the application. For example, this contains built CSS.
- Views/
Views contains all views used by the application
## Software Stack ## Software Stack
@ -32,14 +62,6 @@ The database used is MariaDB 10.4.8
## External frontend libraries used ## External frontend libraries used
### Vue.js
Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web
Vue.js allows for interactivity while being less cumbersome than manipulating the DOM manually e.g. with jQuery.
[Project Website](https://vuejs.org)
### jQuery ### jQuery
jQuery is a feature-rich JavaScript library. jQuery is a feature-rich JavaScript library.

View File

@ -1,12 +0,0 @@
<?php
namespace Application\Controllers;
use Mitsumine\HTTP\Request;
class IndexController {
public function index(Request $request) {
return [
'mitsumine' => 'yuika'
];
}
}

View File

@ -1,6 +0,0 @@
<?php
namespace Mitsumine\HTTP;
class Request {
}

View File

@ -1,12 +0,0 @@
<?php
namespace Mitsumine\Services;
use Mitsumine\Services\ServiceContainer;
class Database {
private $conn;
public function __construct() {
$config = ServiceContainer::Config();
$this->conn = mysqli_connect($config->db_host(),$config->db_user(),$config->db_pass(),$config->db_name());
}
}

View File

@ -1,48 +0,0 @@
<?php
require 'autoload.php';
// Use helper classes from Mitsumine
use Mitsumine\HTTP\Request;
use Mitsumine\Services\ServiceContainer;
ServiceContainer::Database();
// Get all routes
$routes = require 'routes.php';
// Get request URI
$uri = $_SERVER['PHP_SELF'];
// Cut off index.php
$uri = substr($uri,strlen('/index.php'),strlen($uri)-strlen('/index.php'));
// Build request object to pass to controller
$request = new Request();
$request_method = $_SERVER['REQUEST_METHOD'];
// Get current route from uri
$route = $routes[$request_method.':'.$uri];
// Duar (actually, split the method string to class name and method name)
$method_part = explode("@",$route['controller']);
// Get class name and method name
$class = $method_part[0];
$method = $method_part[1];
// Get fully qualified class name of route
$fqcn = 'Application\\Controllers\\'.$class;
$controller = new $fqcn();
// Execute method specified in route
$result = $controller->$method($request);
// Convert array to JSON
if(is_array($result)) {
header('Content-Type: application/json');
$result = json_encode($result);
}
echo $result;

View File

@ -1,6 +0,0 @@
<?php
return [
'GET:/api' => [
'controller' => 'IndexController@index',
],
];

View File

@ -1,18 +0,0 @@
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
{{ message }}
</div>
<script>
var app = new Vue({
el: '#app',
data: {
message: 'Yuika!'
}
})
</script>
</body>
</html>

View File

@ -1,18 +1,65 @@
<?php <?php
require 'autoload.php';
// Use helper classes from Application
use Application\HTTP\Request;
use Application\HTTP\Response;
use Application\Services\ServiceContainer;
ServiceContainer::Database();
ServiceContainer::Session();
// Get all routes
$routes = require 'routes.php';
// Get request URI // Get request URI
$uri = $_SERVER['PHP_SELF']; $uri = $_SERVER['PHP_SELF'];
// Cut off index.php // Cut off index.php
$uri = substr($uri,strlen('/index.php'),strlen($uri)-strlen('/index.php')); $uri = substr($uri,strlen('/index.php'),strlen($uri)-strlen('/index.php'));
if(strpos($uri,'/api') !== false && strpos($uri,'/api') == 0) {
include 'backend/index.php'; // Serve static files first
} else { if(file_exists('Application/Static'.$uri) && $uri != '') {
// Remove trailing slashes readfile('Application/Static'.$uri);
exit;
}
// Remove trailing slash
if(substr($uri,strlen($uri)-1,1) == '/') { if(substr($uri,strlen($uri)-1,1) == '/') {
$uri = substr($uri,0,strlen($uri)-1); $uri = substr($uri,0,strlen($uri)-1);
} }
$file = 'frontend'.$uri.'.html';
if(!file_exists($file)) { // Build request object to pass to controller
$file = 'frontend'.$uri.'/index.html'; $request = new Request();
}
readfile($file); $response = new Response();
$request_method = $_SERVER['REQUEST_METHOD'];
// Get current route from uri
if(!array_key_exists($request_method.':'.$uri,$routes)) {
http_response_code(404);
header('Content-Type: application/json');
die(json_encode([ 'success' => false, 'error' => 'Not found' ]));
} }
$route = $routes[$request_method.':'.$uri];
// Duar (actually, split the method string to class name and method name)
$method_part = explode("@",$route['controller']);
// Get class name and method name
$class = $method_part[0];
$method = $method_part[1];
// Get fully qualified class name of route
$fqcn = 'Application\\Controllers\\'.$class;
$controller = new $fqcn();
$response = $controller->$method($request,$response);
$response->render();
// Convert array to JSON

27
routes.php Normal file
View File

@ -0,0 +1,27 @@
<?php
return [
'GET:' => [
'controller' => 'IndexController@index',
],
'GET:/signup' => [
'controller' => 'AuthController@sign_up',
],
'POST:/signup' => [
'controller' => 'AuthController@create_user',
],
'GET:/signup/success' => [
'controller' => 'AuthController@sign_up_success',
],
'GET:/signup/confirm' => [
'controller' => 'AuthController@sign_up_confirm',
],
'GET:/login' => [
'controller' => 'AuthController@login',
],
'POST:/login' => [
'controller' => 'AuthController@login_check',
],
'GET:/logout' => [
'controller' => 'AuthController@logout',
],
];