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,27 +1,57 @@
# 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
The main handler of backend functions. Handles routing, loading of Mitsumine services, and conversion of array responses to JSON.
- 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 contains classes that are the core of the application itself
- Controllers/
Controllers contain controllers that return HTTP responses
- frontend/
The frontend of this application, written in HTML and utilizes T
- index.php - index.php
This index file allows serving both frontend and backend from one endpoint. The main handler of backend functions. Handles routing, loading of Application services, and response handling.
- Application/
Application contains classes that are the core of the application itself
- Assets/
Assets contains buildable assets, for example source CSS.
- Controllers/
Controllers contain controllers that return HTTP responses
- Foundations/
Foundations are helper classes for various functions, such as an SQL query builder, and base model implementation;
- SQLHelper
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
The software is tested on the Apache server and PHP 7.3 on Arch Linux. The software is tested on the Apache server and PHP 7.3 on Arch Linux.
@ -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);
if(substr($uri,strlen($uri)-1,1) == '/') { exit;
$uri = substr($uri,0,strlen($uri)-1);
}
$file = 'frontend'.$uri.'.html';
if(!file_exists($file)) {
$file = 'frontend'.$uri.'/index.html';
}
readfile($file);
} }
// Remove trailing slash
if(substr($uri,strlen($uri)-1,1) == '/') {
$uri = substr($uri,0,strlen($uri)-1);
}
// Build request object to pass to controller
$request = new Request();
$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',
],
];