Webservices

This commit is contained in:
2026-01-31 18:01:24 +01:00
parent e1c752fcf8
commit 2d2fc24d71
35 changed files with 3531 additions and 0 deletions

1
services/www/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
gallery-uploads

2
services/www/README.md Normal file
View File

@@ -0,0 +1,2 @@
# Dependencies
`apt install imagemagick`

View File

@@ -0,0 +1,27 @@
<?php
class Authenticator {
private $db;
function __construct($db) {
$this->db = $db;
}
public function authenticate() {
$token = $_SERVER['HTTP_AUTHENTICATION'];
$query = "SELECT user_id FROM token WHERE token=:token AND expires>NOW() LIMIT 1";
$stmt = $this->db->prepare($query);
$stmt->bindParam(":token", $token);
if($stmt->execute()){
if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
// Token found
return $row['user_id'];
}
}
http_response_code(401);
echo json_encode(array("error" => "Unauthorized"));
exit();
return 0;
}
}

View File

@@ -0,0 +1 @@
LimitRequestBody 20480000

View File

@@ -0,0 +1,82 @@
<?php
const ITEMS_PER_PAGE = 50;
// required headers
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
// include database and object files
include_once '../../config/database.php';
include_once '../objects/gallery_item.php';
include_once '../objects/user.php';
include_once '../objects/like.php';
include_once '../authenticator.php';
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
// CORS Pre-flight request
header("Access-Control-Allow-Headers: origin, content-type, accept, authentication");
http_response_code(200);
exit();
} else if ($_SERVER['REQUEST_METHOD'] != 'GET') {
http_response_code(400);
echo json_encode(array("error" => "Method not accepted."));
exit();
}
// get params
$page = 0;
if (isset($_GET["page"])) {
if (!is_int($_GET["page"])) {
http_response_code(400);
}
$page = $_GET["page"];
}
$id = null;
if (isset($_GET['id'])) {
if (!is_int($_GET["id"])) {
http_response_code(400);
}
$id = $_GET["id"];
}
// instantiate database and product object
$database = new Database();
$db = $database->getConnection();
$auth = new Authenticator($db);
$uid = $auth->authenticate();
// query products
if ($id)
$stmt = GalleryItem::readById($db, $uid, $id);
else
$stmt = GalleryItem::read($db, $uid, $page, ITEMS_PER_PAGE);
// products array
$gi_arr=array();
$gi_arr["records"]=array();
$gi_arr["page"]=$page;
$gi_arr["more"]=FALSE;
// retrieve our table contents
// fetch() is faster than fetchAll()
// http://stackoverflow.com/questions/2770630/pdofetchall-vs-pdofetch-in-a-loop
$count = 0;
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)){
$count++;
$gallery_item = GalleryItem::fromRow($row);
if ($count <= ITEMS_PER_PAGE) {
array_push($gi_arr["records"], $gallery_item);
} else {
$gi_arr["more"] = TRUE; // If the query returns one more element than ITEMS_PER_PAGE, there is at least another page
}
}
// set response code - 200 OK
http_response_code(200);
// show products data in json format
echo json_encode($gi_arr);

View File

@@ -0,0 +1,192 @@
<?php
/**
* ALERT! Requires settings.json file in the static folder!
*/
const GALLERY_UPLOAD_PATH = "gallery-uploads";
const GALLERY_ITEM_SIZE_THUMB = 720;
const GALLERY_ITEM_SIZE_DIST = 4096;
const SETTINGS_STATIC_JSON_PATH = "../../static/settings.json";
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
include_once '../../config/database.php';
include_once '../objects/gallery_item.php';
include_once '../objects/user.php';
include_once '../objects/like.php';
include_once '../authenticator.php';
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
http_response_code(400);
echo json_encode(array("error" => "Method not accepted."));
exit();
}
$database = new Database();
$db = $database->getConnection();
$auth = new Authenticator($db);
$userId = $auth->authenticate();
// Check the image sharing is already enabled
$settings = json_decode(file_get_contents(SETTINGS_STATIC_JSON_PATH));
if (!$settings->photoSharingEnabled) {
// Sharing not yet enabled: check the user is an Admin
$stmt = User::get($db, $userId);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$user || !array_key_exists('admin', $user))
throw new Exception('unable to determine if user ' . $userId . ' is an admin');
if (!$user['admin']) {
http_response_code(403);
echo json_encode(array("error" => "User doesn't have permission to publish an image"));
exit();
}
}
try {
// Undefined | Multiple Files | $_FILES Corruption Attack
// If this request falls under any of them, treat it invalid.
if (
!isset($_FILES['image']['error']) ||
is_array($_FILES['image']['error'])
) {
throw new InvalidArgumentException('Invalid parameters.');
}
// Check $_FILES['image']['error'] value.
switch ($_FILES['image']['error']) {
case UPLOAD_ERR_OK:
break;
case UPLOAD_ERR_NO_FILE:
throw new RuntimeException('No file sent.');
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
throw new RuntimeException('Exceeded filesize limit.');
default:
throw new RuntimeException('Unknown errors.');
}
// DO NOT TRUST $_FILES['image']['mime'] VALUE !!
// Check MIME Type by yourself.
$finfo = new finfo(FILEINFO_MIME_TYPE);
if (false === $ext = array_search(
$finfo->file($_FILES['image']['tmp_name']),
array(
'jpg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
),
true
)) {
throw new RuntimeException('Invalid file format.');
}
// Create upload directory (if not exists)
$dir = GALLERY_UPLOAD_PATH;
$dirRelPath = '../../' . $dir;
// create new directory with 744 permissions if it does not exist yet
// owner will be the user/group the PHP script is run under
if ( !file_exists($dirRelPath) ) {
if (!mkdir ($dirRelPath, 0744)) {
throw new RuntimeException('Unable to create uploads folder: '.$dirRelPath);
}
}
if ( !file_exists($_FILES['image']['tmp_name'])) {
throw new RuntimeException('Failed not present in temp folder: '.$_FILES['image']['tmp_name']);
}
// Save original image
$destFileName = sha1_file($_FILES['image']['tmp_name']);
$destPath = sprintf('%s/%s.%s',
$dirRelPath,
$destFileName,
$ext
);
if (!move_uploaded_file(
$_FILES['image']['tmp_name'],
$destPath
)) {
throw new RuntimeException('Failed to move uploaded file to '.$destPath);
}
// Create distribution image
$distDestPath = sprintf('%s/%s_dist.%s',
$dirRelPath,
$destFileName,
$ext
);
$retCodeThumb = exec('convert "'.$destPath.'" -auto-orient -resize '.GALLERY_ITEM_SIZE_DIST.'x'.GALLERY_ITEM_SIZE_DIST.' -quality 80 "'.$distDestPath.'"');
if ($retCodeThumb)
throw new RuntimeException("Unable to create distribution image");
// Create thumb
$thumbDestPath = sprintf('%s/%s_thumb.%s',
$dirRelPath,
$destFileName,
$ext
);
$retCodeThumb = exec('convert "'.$destPath.'" -auto-orient -resize '.GALLERY_ITEM_SIZE_THUMB.'x'.GALLERY_ITEM_SIZE_THUMB.' -quality 80 "'.$thumbDestPath.'"');
if ($retCodeThumb)
throw new RuntimeException("Unable to create thumbnail");
// Create gallery item with uploaded photo (only dist and thumb paths are saved to db)
$gi = [];
$gi['imageUrl'] = sprintf('/%s/%s_dist.%s',
$dir,
$destFileName,
$ext
);
$gi['imageThumbUrl'] = sprintf('/%s/%s_thumb.%s',
$dir,
$destFileName,
$ext
);
$gi['likes ']= 0;
$gi['description'] = ""; // TODO
$gi['author'] = $userId;
$db->beginTransaction();
if (!GalleryItem::create($db, $gi)){
throw new RuntimeException("Unable to create GalleryItem.");
}
// Retrieve inserted item
$newGiId = $database->conn->lastInsertId();
$stmt = GalleryItem::readById($db, $userId, $newGiId);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row)
throw new RuntimeException('Inserted row could not be found!');
$result=array(
"success" => TRUE,
"errorMessage" => "",
"filename" => $destFileName,
"filepath" => $destPath,
"record" => GalleryItem::fromRow($row)
);
$db->commit();
// set response code - 200 OK
http_response_code(200);
// show products data in json format
echo json_encode($result);
} catch (RuntimeException $e) {
$err_item=array(
"success" => FALSE,
"errorMessage" => $e->getMessage(),
"filename" => "",
"filepath" => "",
);
echo json_encode($err_item);
}
?>

View File

@@ -0,0 +1,56 @@
<?php
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
include_once '../../config/database.php';
include_once '../objects/like.php';
include_once '../authenticator.php';
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
http_response_code(400);
echo json_encode(array("error" => "Method not accepted."));
exit();
}
$database = new Database();
$db = $database->getConnection();
$auth = new Authenticator($db);
$userId = $auth->authenticate();
// get posted data
$data = json_decode(file_get_contents("php://input"));
if(!empty($data->gallery_id)) {
$existentLike = Like::get($db, $userId, $data->gallery_id);
if ($existentLike) {
// Like already exists, return the existent one
http_response_code(201);
echo json_encode($existentLike);
return;
}
$like = new Like($db);
$like->user_id = $userId;
$like->gallery_id = $data->gallery_id;
$id = $like->create();
if($id){
$like->id = $id;
// set response code - 201 created
http_response_code(201);
echo json_encode($like);
} else {
// unable to create
http_response_code(500);
echo json_encode(array("error" => "Unable to create Like."));
}
} else {
// Missing parameters
http_response_code(400);
echo json_encode(array("error" => "Unable to create Like. Missing gallery_id."));
}
?>

View File

@@ -0,0 +1,50 @@
<?php
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
include_once '../../config/database.php';
include_once '../objects/like.php';
include_once '../authenticator.php';
if ($_SERVER['REQUEST_METHOD'] != 'DELETE') {
http_response_code(400);
echo json_encode(array("error" => "Method not accepted."));
exit();
}
$database = new Database();
$db = $database->getConnection();
$auth = new Authenticator($db);
$userId = $auth->authenticate();
if(array_key_exists("id", $_GET) && $_GET["id"]) {
$db->beginTransaction();
// Get like before deleting
$existentLike = Like::byUserAndId($db, $userId, $_GET["id"]);
if(!$existentLike) {
http_response_code(404);
return;
}
$success = Like::delete($db, $_GET["id"]);
$db->commit();
if($success){
// set response code - 200 ok
echo json_encode($existentLike);
http_response_code(200);
} else {
// unable to create
http_response_code(500);
echo json_encode(array("error" => "Unable to delete Like."));
}
} else {
// Missing parameters
http_response_code(400);
echo json_encode(array("error" => "Unable to delete Like. Missing id."));
}
?>

View File

@@ -0,0 +1,128 @@
<?php
class GalleryItem {
private const table_name = "gallery";
private const select = "
SELECT
g.*,
(select count(id) from gallery_like where gallery_id = g.id) as likes,
lu.id as lu_id,
lu.name as lu_name,
lu.surname as lu_surname,
lu.code as lu_code,
lu.picture as lu_picture,
lu.admin as lu_admin,
lu.table as lu_table,
lu.witness as lu_witness,
lu.invited_by as lu_invited_by,
au.id as au_id,
au.name as au_name,
au.surname as au_surname,
au.code as au_code,
au.picture as au_picture,
au.admin as au_admin,
au.table as au_table,
au.witness as au_witness,
au.invited_by as au_invited_by,
glcu.id as glcu_id,
glcu.gallery_id as glcu_gallery_id,
glcu.user_id as glcu_user_id,
glcu.created as glcu_created
FROM " . self::table_name . " g
-- Author
LEFT JOIN `user` au
ON au.id = g.author
-- First liked user
LEFT JOIN gallery_like gl
ON gl.gallery_id = g.id
LEFT JOIN `user` lu
ON lu.id = gl.user_id
-- Like from current user
LEFT JOIN gallery_like glcu
ON glcu.gallery_id = g.id
AND glcu.user_id = :uid
";
static function read($db, $uid, $page, $elemPerPage){
$start = $page * $elemPerPage;
$limit = $elemPerPage + 1; // Retrieve one more item to know if there are elements and compute "more" value
$query = self::select . "
GROUP BY g.id
ORDER BY created DESC
LIMIT :start,:limit";
// prepare query statement
$stmt = $db->prepare($query);
$stmt->bindParam(":uid", $uid);
$stmt->bindParam(":start", $start, PDO::PARAM_INT);
$stmt->bindParam(":limit", $limit, PDO::PARAM_INT);
// execute query
$stmt->execute();
return $stmt;
}
static function readById($db, $uid, $id){
$query = self::select . "
WHERE g.id = :id
GROUP BY g.id
ORDER BY created DESC
";
// prepare query statement
$stmt = $db->prepare($query);
$stmt->bindParam(":id", $id);
$stmt->bindParam(":uid", $uid);
// execute query
$stmt->execute();
return $stmt;
}
static function create($db, $item){
$query = "INSERT INTO " . self::table_name . "
SET image_url=:imageUrl, image_thumb_url=:imageThumbUrl, description=:description, author=:author";
// prepare query
$stmt = $db->prepare($query);
// sanitize
$imageUrl=htmlspecialchars(strip_tags($item['imageUrl']));
$imageThumbUrl=htmlspecialchars(strip_tags($item['imageThumbUrl']));
$description=htmlspecialchars(strip_tags($item['description']));
$author=htmlspecialchars(strip_tags($item['author']));
// bind values
$stmt->bindParam(":imageUrl", $imageUrl);
$stmt->bindParam(":imageThumbUrl", $imageThumbUrl);
$stmt->bindParam(":description", $description);
$stmt->bindParam(":author", $author);
// execute query
if($stmt->execute()){
return true;
}
return false;
}
static function fromRow($row) {
return array(
"id" => $row['id'],
"imageUrl" => $row['image_url'],
"imageThumbUrl" => $row['image_thumb_url'],
"likes" => $row['likes'],
"firstUserLiked" => User::createFromRow($row, 'lu'),
"description" => $row['description'],
"author" => User::createFromRow($row, 'au'),
"created" => $row['created'],
"currentUserLike" => Like::createFromRow($row, 'glcu'),
);
}
}
?>

View File

@@ -0,0 +1,101 @@
<?php
class Like {
private $conn;
private const table_name = "gallery_like";
public $id;
public $gallery_id;
public $user_id;
public $created;
public function __construct($db){
$this->conn = $db;
}
function create() {
$query = "INSERT INTO " . self::table_name . "
SET gallery_id=:galleryId, user_id=:userId";
// prepare query
$stmt = $this->conn->prepare($query);
$this->created=(new \DateTime())->format('Y-m-d H:i:s');
// bind values
$stmt->bindParam(":userId", $this->user_id);
$stmt->bindParam(":galleryId", $this->gallery_id);
// execute query
if($stmt->execute()){
return $this->conn->lastInsertId();
}
return false;
}
static function delete($db, $id) {
$query = "DELETE FROM " . self::table_name . " WHERE id = :id";
$stmt = $db->prepare($query);
$stmt->bindParam(":id", $id);
if($stmt->execute()){
return true;
}
return false;
}
public static function createFromRow($row, $prefix = '') {
if ($prefix)
$prefix = $prefix . '_';
else
$prefix = '';
if (!$row[$prefix . 'id'])
return null;
$instance = new self(null);
$instance->id = $row[$prefix . 'id'];
$instance->gallery_id = $row[$prefix . 'gallery_id'];
$instance->user_id = $row[$prefix . 'user_id'];
$instance->created = $row[$prefix . 'created'];
return $instance;
}
public static function get($db, $uid, $gid) {
$q = "
SELECT *
FROM " . self::table_name . "
WHERE
user_id = :uid AND
gallery_id = :gid
";
$stmt = $db->prepare($q);
$stmt->bindParam(":uid", $uid);
$stmt->bindParam(":gid", $gid);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row)
return self::createFromRow($row);
return false;
}
public static function byUserAndId($db, $uid, $id) {
$q = "
SELECT *
FROM " . self::table_name . "
WHERE
user_id = :uid AND
id = :id
";
$stmt = $db->prepare($q);
$stmt->bindParam(":uid", $uid);
$stmt->bindParam(":id", $id);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row)
return self::createFromRow($row);
return false;
}
}

View File

@@ -0,0 +1,65 @@
<?php
class Presence {
// database connection and table name
private $conn;
private $table_name = "presence";
// object properties
public $id;
public $userId;
public $willBePresent = false;
public $notes = null;
public $created;
// constructor with $db as database connection
public function __construct($db){
$this->conn = $db;
}
static function read($db, $uid){
$query = "
SELECT will_be_present, notes
FROM presence
WHERE user_id = :uid
ORDER BY created DESC
LIMIT 1";
// prepare query statement
$stmt = $db->prepare($query);
$stmt->bindParam(":uid", $uid);
// execute query
$stmt->execute();
return $stmt;
}
function create(){
$query = "INSERT INTO " . $this->table_name . "
SET user_id=:userId, will_be_present=:willBePresent, notes=:notes";
// prepare query
$stmt = $this->conn->prepare($query);
// sanitize
$this->userId=$this->userId;
$this->created=(new \DateTime())->format('Y-m-d H:i:s');
// bind values
$stmt->bindParam(":userId", $this->userId);
$wbp = $this->willBePresent ? 1 : 0;
$stmt->bindParam(":willBePresent", $wbp);
$stmt->bindParam(":notes", $this->notes);
// execute query
if($stmt->execute()){
return true;
}
return false;
}
}
?>

View File

@@ -0,0 +1,43 @@
<?php
class Token {
private $conn;
private $table_name = "token";
public $userId;
public $user;
public $token;
public $created;
public $expires;
// constructor with $db as database connection
public function __construct($db){
$this->conn = $db;
}
function create(){
$params = "user_id=:userId, token=:token, created=:created, expires=:expires";
$query = "INSERT INTO " . $this->table_name . "
SET " . $params;
// prepare query
$stmt = $this->conn->prepare($query);
$this->created=(new \DateTime())->format('Y-m-d H:i:s');
$this->expires=(new \DateTime())->add(new DateInterval('P1Y3M'))->format('Y-m-d H:i:s');
$this->token=md5(sprintf('%d-%s', $this->userId, $this->expires));
// bind values
$stmt->bindParam(":userId", $this->userId);
$stmt->bindParam(":token", $this->token);
$stmt->bindParam(":created", $this->created);
$stmt->bindParam(":expires", $this->expires);
// execute query
if($stmt->execute()){
return true;
}
return false;
}
}

View File

@@ -0,0 +1,62 @@
<?php
class User {
private $conn;
private $table_name = "user";
public $id;
public $name;
public $surname;
public $code;
public $picture;
public $admin;
public $table;
public $witness;
public $invited_by;
// constructor with $db as database connection
public function __construct($db){
$this->conn = $db;
}
static function get($db, $uid){
$query = "
SELECT *
FROM user AS u
WHERE u.id = :uid;
";
// prepare query statement
$stmt = $db->prepare($query);
$stmt->bindParam(":uid", $uid);
// execute query
$stmt->execute();
return $stmt;
}
public static function createFromRow($row, $prefix = '') {
if ($prefix)
$prefix = $prefix . '_';
else
$prefix = '';
// If user is null
if (!$row[$prefix . 'id'])
return null;
$instance = new self(null);
$instance->id = $row[$prefix . 'id'];
$instance->name = $row[$prefix . 'name'];
$instance->surname = $row[$prefix . 'surname'];
$instance->code = $row[$prefix . 'code'];
$instance->picture = $row[$prefix . 'picture'];
$instance->admin = $row[$prefix . 'admin'] ? true : false;
$instance->table = $row[$prefix . 'table'];
$instance->witness = $row[$prefix . 'witness'] ? true : false;
$instance->invited_by = $row[$prefix . 'invited_by'];
return $instance;
}
}

View File

@@ -0,0 +1,42 @@
<?php
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
include_once '../../config/database.php';
include_once '../objects/presence.php';
include_once '../authenticator.php';
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
http_response_code(400);
echo json_encode(array("error" => "Method not accepted."));
exit();
}
$database = new Database();
$db = $database->getConnection();
$auth = new Authenticator($db);
$userId = $auth->authenticate();
// get posted data
$data = json_decode(file_get_contents("php://input"));
$p = new Presence($db);
$p->userId = $userId;
$p->willBePresent = $data->willBePresent;
$p->notes = $data->notes;
if($p->create()){
// set response code - 201 created
http_response_code(201);
echo json_encode($p);
} else {
// unable to create
http_response_code(500);
echo json_encode(array("error" => "Unable to create Presence."));
}
?>

View File

@@ -0,0 +1,51 @@
<?php
/**
* Returns the current user response to the presence question.
* Works as a list because the id should not be needed for the query.
*/
// required headers
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
// include database and object files
include_once '../../config/database.php';
include_once '../objects/presence.php';
include_once '../objects/user.php';
include_once '../authenticator.php';
if ($_SERVER['REQUEST_METHOD'] != 'GET') {
http_response_code(400);
echo json_encode(array("error" => "Method not accepted."));
exit();
}
// instantiate database and product object
$database = new Database();
$db = $database->getConnection();
$auth = new Authenticator($db);
$uid = $auth->authenticate();
// query products
$stmt = Presence::read($db, $uid);
$resp=array();
$resp["records"]=array();
$resp["page"]=1;
$resp["more"]=FALSE;
$presence = $stmt->fetch(PDO::FETCH_ASSOC);
if ($presence) {
$resp["records"][] = [
'willBePresent' => $presence['will_be_present'] ? true : false,
'notes' => $presence['notes']
];
}
// set response code - 200 OK
http_response_code(200);
// show products data in json format
echo json_encode($resp);

View File

@@ -0,0 +1,71 @@
<?php
// required headers
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
// include database and object files
include_once '../../config/database.php';
include_once '../authenticator.php';
/**
* select all users (name, surname, table) sharing the same table
*/
function getTablePeople($db, $uid) {
$q = '
SELECT name, surname, `table`
FROM `user` u
WHERE
u.`table` = (
SELECT `table` FROM `user` WHERE id = :uid
) AND
u.id <> :uid
';
// prepare query statement
$stmt = $db->prepare($q);
$stmt->bindParam(":uid", $uid);
// execute query
$stmt->execute();
$result = [
'count' => 0,
'people' => [],
'table' => null,
];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)){
$result['count']++;
$result['people'][] = [
'name' => $row['name'],
'surname' => $row['surname'],
];
$result['table'] = $row['table'];
}
return $result;
}
/**
* Method implementation
*/
if ($_SERVER['REQUEST_METHOD'] != 'GET') {
http_response_code(400);
echo json_encode(array("error" => "Method not accepted."));
exit();
}
// instantiate database
$database = new Database();
$db = $database->getConnection();
$auth = new Authenticator($db);
$uid = $auth->authenticate();
$result = getTablePeople($db, $uid);
// set response code - 200 OK
http_response_code(200);
// show products data in json format
echo json_encode($result);

View File

@@ -0,0 +1,61 @@
<?php
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
include_once '../../config/database.php';
include_once '../objects/token.php';
include_once '../objects/user.php';
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
http_response_code(400);
echo json_encode(array("error" => "Method not accepted."));
exit();
}
$database = new Database();
$db = $database->getConnection();
$token = new Token($db);
// get posted data
$data = json_decode(file_get_contents("php://input"));
if(!empty($data->code)){
// Check user existence
$query = "SELECT * FROM user WHERE UPPER(code) = UPPER(:code) LIMIT 1";
$stmt = $db->prepare($query);
$stmt->bindParam(":code", $data->code);
if($stmt->execute()){
if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
// User found
$token->userId = $row['id'];
$token->user = User::createFromRow($row);
if($token->create()){
// set response code - 201 created
http_response_code(201);
echo json_encode($token);
} else {
// unable to create
http_response_code(500);
echo json_encode(array("error" => "Unable to create Token."));
}
} else {
// User not found
// Wait 5 secs to slow down bruteforce attacks
sleep(5);
http_response_code(404);
echo json_encode(array("error" => "Unable to create Token. User not found."));
}
}
} else {
// Missing parameters
http_response_code(400);
echo json_encode(array("error" => "Unable to create Token. code is mandatory."));
}
?>

1
services/www/config/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
database.php

View File

@@ -0,0 +1,25 @@
<?php
class Database{
private $host = "localhost";
private $db_name = "your_db_name";
private $username = "your_db_user";
private $password = "your_db_password";
public $conn;
// get the database connection
public function getConnection(){
$this->conn = null;
try{
$this->conn = new PDO("mysql:host=" . $this->host . ";dbname=" . $this->db_name, $this->username, $this->password);
$this->conn->exec("set names utf8");
}catch(PDOException $exception){
echo "Connection error: " . $exception->getMessage();
}
return $this->conn;
}
}
?>

1947
services/www/php.ini Normal file

File diff suppressed because it is too large Load Diff

77
services/www/report.php Normal file
View File

@@ -0,0 +1,77 @@
<?php
include_once 'config/database.php';
$USER = 'admin';
$PASSWORD = 'ChinitaCiambella';
// Request auth
if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW']) || $_SERVER['PHP_AUTH_USER'] !== $USER || $_SERVER['PHP_AUTH_PW'] !== $PASSWORD) {
header('WWW-Authenticate: Basic realm="WeddingReport"');
header('HTTP/1.0 401 Unauthorized');
echo 'Authentication required';
exit;
}
// Obtain report from database
$database = new Database();
$db = $database->getConnection();
$query = "
-- Report
SELECT
u.name,
u.surname,
(SELECT t.created FROM token t WHERE t.user_id = u.id ORDER BY t.created ASC LIMIT 1) AS started_app,
CASE WHEN p.will_be_present IS NULL THEN '⏳' ELSE (
CASE WHEN p.will_be_present THEN '✅' ELSE '❌' END
) END as will_be_present,
p.created as when_user_answered,
p.notes
FROM `user` u
LEFT JOIN presence p
ON p.user_id = u.id
GROUP BY u.id
ORDER BY when_user_answered DESC, started_app DESC
";
$stmt = $db->prepare($query);
if (!$stmt->execute()) {
echo("<html><body><h1>Error running query!</h1></body></html>");
exit;
}
?>
<html>
<head>
<title>Wedding report</title>
<style>
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
th {
background-color: #96D4D4;
}
</style>
</head>
<body>
<h1>Wedding report</h1>
<table>
<tr>
<th>Name</th><th>Surname</th><th>Used app</th><th>Will be present</th><th>When answered</th><th>Notes</th>
</tr>
<?php
// Draw table rows
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)){
?>
<tr>
<td><?php echo($row['name']); ?></td>
<td><?php echo($row['surname']); ?></td>
<td><?php echo($row['started_app']); ?></td>
<td style="text-align:center"><?php echo($row['will_be_present']); ?></td>
<td><?php echo($row['when_user_answered']); ?></td>
<td><?php echo($row['notes']); ?></td>
</tr>
<?php
}
?>
</table>
</body>
</html>

View File

@@ -0,0 +1,20 @@
{
"localized": {
"en": {
"name": "Wedding list",
"content": "<p><i>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</i></p><p>Integer pretium venenatis arcu vel gravida. Praesent accumsan nec augue non aliquet. Praesent egestas nulla dui, in lobortis ex facilisis ac.</p>",
"picture": "attendee-gift.jpg"
},
"it": {
"name": "Lista nozze",
"content": "<p><i>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</i></p><p>Integer pretium venenatis arcu vel gravida. Praesent accumsan nec augue non aliquet. Praesent egestas nulla dui, in lobortis ex facilisis ac.</p>",
"picture": "attendee-gift.jpg"
},
"es": {
"name": "Lista de boda",
"content": "<p><i>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</i></p><p>Integer pretium venenatis arcu vel gravida. Praesent accumsan nec augue non aliquet. Praesent egestas nulla dui, in lobortis ex facilisis ac.</p>",
"picture": "attendee-gift.jpg"
}
},
"version": 1
}

View File

@@ -0,0 +1,20 @@
{
"localized": {
"en": {
"name": "Food",
"content": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer pretium venenatis arcu vel gravida. Praesent accumsan nec augue non aliquet. Praesent egestas nulla dui, in lobortis ex facilisis ac. Donec at diam eu dolor pretium tristique. Vivamus mi quam, dignissim quis ex posuere, porta pellentesque nulla. Mauris vitae dictum risus, et porttitor nisi. Proin elementum maximus purus vitae sodales. Vestibulum cursus, arcu ut suscipit lobortis, libero lacus efficitur lorem, at ultrices magna leo id sem. Etiam aliquet felis sit amet neque maximus euismod. Nam a vestibulum turpis. Morbi commodo fermentum odio, a convallis neque efficitur ullamcorper.</p>",
"picture": "food-menu.jpg"
},
"it": {
"name": "Il menu nuziale",
"content": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer pretium venenatis arcu vel gravida. Praesent accumsan nec augue non aliquet. Praesent egestas nulla dui, in lobortis ex facilisis ac. Donec at diam eu dolor pretium tristique. Vivamus mi quam, dignissim quis ex posuere, porta pellentesque nulla. Mauris vitae dictum risus, et porttitor nisi. Proin elementum maximus purus vitae sodales. Vestibulum cursus, arcu ut suscipit lobortis, libero lacus efficitur lorem, at ultrices magna leo id sem. Etiam aliquet felis sit amet neque maximus euismod. Nam a vestibulum turpis. Morbi commodo fermentum odio, a convallis neque efficitur ullamcorper.</p>",
"picture": "food-menu.jpg"
},
"es": {
"name": "Menú de boda",
"content": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer pretium venenatis arcu vel gravida. Praesent accumsan nec augue non aliquet. Praesent egestas nulla dui, in lobortis ex facilisis ac. Donec at diam eu dolor pretium tristique. Vivamus mi quam, dignissim quis ex posuere, porta pellentesque nulla. Mauris vitae dictum risus, et porttitor nisi. Proin elementum maximus purus vitae sodales. Vestibulum cursus, arcu ut suscipit lobortis, libero lacus efficitur lorem, at ultrices magna leo id sem. Etiam aliquet felis sit amet neque maximus euismod. Nam a vestibulum turpis. Morbi commodo fermentum odio, a convallis neque efficitur ullamcorper.</p>",
"picture": "food-menu.jpg"
}
},
"version": 1
}

View File

@@ -0,0 +1,32 @@
{
"localized": {
"it": {
"name": "Location meravigliosa",
"content": "<b>Lorem ipsum dolor sit amet,</b> consectetur adipiscing elit. Integer pretium venenatis arcu vel gravida. Praesent accumsan nec augue non aliquet. Praesent egestas nulla dui, in lobortis ex facilisis ac. Donec at diam eu dolor pretium tristique. Vivamus mi quam, dignissim quis ex posuere, porta pellentesque nulla. Mauris vitae dictum risus, et porttitor nisi. Proin elementum maximus purus vitae sodales. Vestibulum cursus, arcu ut suscipit lobortis, libero lacus efficitur lorem, at ultrices magna leo id sem. Etiam aliquet felis sit amet neque maximus euismod. Nam a vestibulum turpis. Morbi commodo fermentum odio, a convallis neque efficitur ullamcorper.",
"picture": "location.jpg",
"coordinates": {
"lat": 43.44308,
"lon": 11.09812
}
},
"en": {
"name": "Wonderful location",
"content": "<b>Lorem ipsum dolor sit amet,</b> consectetur adipiscing elit. Integer pretium venenatis arcu vel gravida. Praesent accumsan nec augue non aliquet. Praesent egestas nulla dui, in lobortis ex facilisis ac. Donec at diam eu dolor pretium tristique. Vivamus mi quam, dignissim quis ex posuere, porta pellentesque nulla. Mauris vitae dictum risus, et porttitor nisi. Proin elementum maximus purus vitae sodales. Vestibulum cursus, arcu ut suscipit lobortis, libero lacus efficitur lorem, at ultrices magna leo id sem. Etiam aliquet felis sit amet neque maximus euismod. Nam a vestibulum turpis. Morbi commodo fermentum odio, a convallis neque efficitur ullamcorper.",
"picture": "location.jpg",
"coordinates": {
"lat": 43.44308,
"lon": 11.09812
}
},
"es": {
"name": "Location meravillosa",
"content": "La <b>Taverna di Bibbiano</b> es una quinta (agriturismo) encantadora y romántica ubicada en el corazón de la Toscana, entre Siena y Florencia, con vistas a las torres de San Gimignano.\nAl llegar a la Taverna, un paisaje impresionante: entre viñedos y olivares, colores y olores se mezclan juntos dando al panorama que la rodea, una belleza atemporal...",
"picture": "location.jpg",
"coordinates": {
"lat": 43.44308,
"lon": 11.09812
}
}
},
"version": 1
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

View File

@@ -0,0 +1,184 @@
{
"__comment__": "Positions to show on the map. Type is an enum (ceremony, lunch, mate, liquor, cigars).",
"mapCenterLat": 43.44304,
"mapCenterLon": 11.09807,
"mapZoom": 19,
"localized": {
"en": [
{
"name": "Aperitif",
"descr": "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
"lat": 43.442822,
"lon": 11.097739,
"type": "appetizer",
"time": "12:30 - 13:30"
},
{
"name": "Wedding lunch",
"descr": "Check Tableau de mariage in the app to discover your table!",
"lat": 43.44294,
"lon": 11.09794,
"type": "lunch",
"time": "13:30 - 16:30"
},
{
"name": "Mate and hot beverages",
"descr": "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
"lat": 43.44311,
"lon": 11.09824,
"type": "mate",
"time": "17:00 - 23:00"
},
{
"name": "Liqueurs and chocolate",
"descr": "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
"lat": 43.44302,
"lon": 11.09818,
"type": "liquor",
"time": "17:00 - 23:00"
},
{
"name": "Cigars and Pipe",
"descr": "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
"lat": 43.443121,
"lon": 11.098089,
"type": "cigars",
"time": "17:00 - 23:00"
},
{
"name": "Photo booth",
"descr": "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
"lat": 43.44289,
"lon": 11.09813,
"type": "photo",
"time": "11:00-23:00"
},
{
"name": "Guest book",
"descr": "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
"lat": 43.443248,
"lon": 11.098165,
"type": "book",
"time": "11:00-23:00"
}
],
"it": [
{
"name": "L'Aperitivo",
"descr": "Un aperitivo all'aperto per stuzzicare l'appetito e chiacchierare un po'",
"lat": 43.442822,
"lon": 11.097739,
"type": "appetizer",
"time": "12:30 - 13:30"
},
{
"name": "Il pranzo nuziale",
"descr": "Per sapere qual è il tuo tavolo, consulta la sezione Tableau de mariage nell'app!",
"lat": 43.44294,
"lon": 11.09794,
"type": "lunch",
"time": "13:30 - 16:30"
},
{
"name": "L'angolo del Mate e Tisane",
"descr": "Non c'è niente di meglio che chiacchierare sorseggiando un buon infuso!",
"lat": 43.44311,
"lon": 11.09824,
"type": "mate",
"time": "17:00 - 23:00"
},
{
"name": "L'angolo dei liquori e del cioccolato",
"descr": "Un selezionato assortimento di liquori e pregiati cioccolati",
"lat": 43.44302,
"lon": 11.09818,
"type": "liquor",
"time": "17:00 - 23:00"
},
{
"name": "L'angolo dei sigari e della pipa",
"descr": "Una esperta selezione di sigari toscani e tabacco da pipa",
"lat": 43.443121,
"lon": 11.098089,
"type": "cigars",
"time": "17:00 - 23:00"
},
{
"name": "Photo booth",
"descr": "Scegli gli accessori, scatta una foto con chi vuoi tu e ritrovala nell'app!",
"lat": 43.44289,
"lon": 11.09813,
"type": "photo",
"time": "11:00-23:00"
},
{
"name": "Libro degli ospiti",
"descr": "Scrivi il tuo nome ed inseriscilo nel libro degli ospiti: lasciaci un ricordo prezioso",
"lat": 43.443248,
"lon": 11.098165,
"type": "book",
"time": "11:00-23:00"
}
],
"es": [
{
"name": "El aperitivo",
"descr": "Un aperitivo al aire libre para abrir el apetito y charlar un poco.",
"lat": 43.442822,
"lon": 11.097739,
"type": "appetizer",
"time": "12:30 - 13:30"
},
{
"name": "Almuerzo nupcial",
"descr": "Para saber cuál es tu mesa, consultá la sección <b>Tableau de Mariage</b> en la aplicación",
"lat": 43.44294,
"lon": 11.09794,
"type": "lunch",
"time": "13:30 - 16:30"
},
{
"name": "El rincón de mate y infusiones",
"descr": "¡No hay nada mejor que charlar tomando un buen mate!",
"lat": 43.44311,
"lon": 11.09824,
"type": "mate",
"time": "17:00 - 23:00"
},
{
"name": "El rincón del licor y del chocolate",
"descr": "Una selecta variedad de licores locales y chocolates exquisitos",
"lat": 43.44302,
"lon": 11.09818,
"type": "liquor",
"time": "17:00 - 23:00"
},
{
"name": "El rincón de los puros y de la pipa",
"descr": "Una cuidada selección de puros toscanos y tabaco de pipa",
"lat": 43.443121,
"lon": 11.098089,
"type": "cigars",
"time": "17:00 - 23:00"
},
{
"name": "Photo booth",
"descr": "Elige los accesorios y sácate una foto con quien quieras: la foto aparecerá en la app!",
"lat": 43.44289,
"lon": 11.09813,
"type": "photo",
"time": "11:00-23:00"
},
{
"name": "Libro de invitados",
"descr": "Escribe tu nombre e insértalo en el libro de invitados: déjanos un recuerdo precioso",
"lat": 43.443248,
"lon": 11.098165,
"type": "book",
"time": "11:00-23:00"
}
]
},
"version": 1
}

View File

@@ -0,0 +1,19 @@
{
"__comment__": "Sets app settings from server. You can specify the location's wifi SSIDs to allow guests to connect without having to enter the password.",
"version": 1,
"appVersion": 1,
"photoSharingEnabled": true,
"showTableEnabled": true,
"rotateToExifData": true,
"wifiNetworks": [
{
"ssid": "Restaurant Wi-Fi",
"password": "password123"
},
{
"ssid": "Church Wi-Fi",
"password": "password123"
}
]
}

View File

@@ -0,0 +1,20 @@
{
"localized": {
"en": {
"name": "Le bomboniere",
"content": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer pretium venenatis arcu vel gravida. Praesent accumsan nec augue non aliquet. Praesent egestas nulla dui, in lobortis ex facilisis ac.</p>",
"picture": "wedding-gift.jpg"
},
"es": {
"name": "Le bomboniere",
"content": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer pretium venenatis arcu vel gravida. Praesent accumsan nec augue non aliquet. Praesent egestas nulla dui, in lobortis ex facilisis ac.</p>",
"picture": "wedding-gift.jpg"
},
"it": {
"name": "Le bomboniere",
"content": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer pretium venenatis arcu vel gravida. Praesent accumsan nec augue non aliquet. Praesent egestas nulla dui, in lobortis ex facilisis ac.</p>",
"picture": "wedding-gift.jpg"
}
},
"version": 1
}