Webservices
This commit is contained in:
26
services/README.md
Normal file
26
services/README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# OpenWedding REST Services
|
||||
|
||||
This folder contains the OpenWedding REST Services.
|
||||
It runs on a LAMP stack.
|
||||
|
||||
## Install
|
||||
|
||||
- Install `imagemagick`, if not already present. Check it is runnable by the www-data user invoking it as `convert`. It is used to resize the images uploaded by the guests.
|
||||
- Create a MySQL db
|
||||
- Create the db structure running `db_schema/sb_sql_schema.sql`
|
||||
- Populate the db with the user
|
||||
- Copy the whole `www` folder in Apache web root
|
||||
- Copy `www/config/database_example.php` to `www/config/database.php`
|
||||
- Edit it according to your configuration (change at least $db_name, $username and $password)
|
||||
- Edit all the json files in `static/`. They can refer to images in the media directory using the image name only.
|
||||
- Replace the images in `media`
|
||||
- Place the guests pictures in `user-pictures`, naming them according to the `picture` column in the `user` table. If the image is missing for some users, the name initials will be shown.
|
||||
|
||||
## Check the guests presence
|
||||
|
||||
Guests can answer from the mobile app to tell if they will be present at the party.
|
||||
You can check the list of the users that answered (or, at least, opened the app) visiting with your web browser the page `report.php`.
|
||||
|
||||
## Retrieve the photos shot during the party
|
||||
|
||||
You'll find all them in `user-pictures`.
|
||||
74
services/db_schema/db_sql_schema.sql
Normal file
74
services/db_schema/db_sql_schema.sql
Normal file
@@ -0,0 +1,74 @@
|
||||
-- Run this query to create the OpenWeddingApp db
|
||||
|
||||
--
|
||||
-- Table structure for table `gallery`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `gallery`;
|
||||
CREATE TABLE `gallery` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
`image_url` varchar(100) NOT NULL,
|
||||
`image_thumb_url` varchar(100) NOT NULL,
|
||||
`description` varchar(100) DEFAULT NULL,
|
||||
`author` int(11) NOT NULL,
|
||||
`created` datetime NOT NULL DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
|
||||
--
|
||||
-- Table structure for table `gallery_like`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `gallery_like`;
|
||||
CREATE TABLE `gallery_like` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`gallery_id` int(11) DEFAULT NULL,
|
||||
`user_id` int(11) DEFAULT NULL,
|
||||
`created` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
|
||||
--
|
||||
-- Table structure for table `presence`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `presence`;
|
||||
CREATE TABLE `presence` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(11) DEFAULT NULL,
|
||||
`will_be_present` tinyint(1) DEFAULT NULL,
|
||||
`notes` text DEFAULT NULL,
|
||||
`created` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
|
||||
--
|
||||
-- Table structure for table `token`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `token`;
|
||||
CREATE TABLE `token` (
|
||||
`token` varchar(255) NOT NULL,
|
||||
`user_id` int(11) NOT NULL,
|
||||
`created` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||
`expires` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
PRIMARY KEY (`token`)
|
||||
);
|
||||
|
||||
--
|
||||
-- Table structure for table `user`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `user`;
|
||||
CREATE TABLE `user` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) NOT NULL,
|
||||
`surname` varchar(255) NOT NULL,
|
||||
`code` varchar(255) NOT NULL,
|
||||
`picture` text DEFAULT NULL,
|
||||
`admin` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`table` varchar(100) NOT NULL,
|
||||
`witness` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`invited_by` varchar(50) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
8
services/db_schema/tests and queries/test_suite.sql
Normal file
8
services/db_schema/tests and queries/test_suite.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
-- Wedding app test suite: creates posts for all users
|
||||
|
||||
insert into gallery (image_url, image_thumb_url,author)
|
||||
select
|
||||
CONCAT('https://picsum.photos/id/', id, '/2000/3000'),
|
||||
CONCAT('https://picsum.photos/id/', id, '/200/300'),
|
||||
id
|
||||
from `user`
|
||||
43
services/db_schema/tests and queries/useful_queries.sql
Normal file
43
services/db_schema/tests and queries/useful_queries.sql
Normal file
@@ -0,0 +1,43 @@
|
||||
-- Useful queries
|
||||
|
||||
-- User presence
|
||||
SELECT
|
||||
u.name,
|
||||
u.surname,
|
||||
CASE WHEN p.will_be_present THEN 'Yes' ELSE 'No' END as will_be_present,
|
||||
p.notes,
|
||||
p.created as when_user_answered
|
||||
FROM presence p
|
||||
LEFT JOIN `user` u
|
||||
ON p.user_id = u.id
|
||||
|
||||
-- User started app
|
||||
SELECT
|
||||
u.name,
|
||||
u.surname,
|
||||
t.created AS logged_in
|
||||
FROM token t
|
||||
LEFT JOIN `user` u
|
||||
ON u.id = t.user_id
|
||||
ORDER BY logged_in ASC
|
||||
|
||||
-- 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
|
||||
|
||||
-- Clear user token
|
||||
DELETE FROM token WHERE user_id = 2
|
||||
|
||||
|
||||
1
services/www/.gitignore
vendored
Normal file
1
services/www/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
gallery-uploads
|
||||
2
services/www/README.md
Normal file
2
services/www/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# Dependencies
|
||||
`apt install imagemagick`
|
||||
27
services/www/api/authenticator.php
Normal file
27
services/www/api/authenticator.php
Normal 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;
|
||||
}
|
||||
}
|
||||
1
services/www/api/gallery_item/.htaccess
Normal file
1
services/www/api/gallery_item/.htaccess
Normal file
@@ -0,0 +1 @@
|
||||
LimitRequestBody 20480000
|
||||
82
services/www/api/gallery_item/read.php
Normal file
82
services/www/api/gallery_item/read.php
Normal 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);
|
||||
192
services/www/api/gallery_item/upload.php
Normal file
192
services/www/api/gallery_item/upload.php
Normal 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);
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
56
services/www/api/like/create.php
Normal file
56
services/www/api/like/create.php
Normal 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."));
|
||||
}
|
||||
|
||||
?>
|
||||
50
services/www/api/like/delete.php
Normal file
50
services/www/api/like/delete.php
Normal 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."));
|
||||
}
|
||||
|
||||
?>
|
||||
128
services/www/api/objects/gallery_item.php
Normal file
128
services/www/api/objects/gallery_item.php
Normal 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'),
|
||||
);
|
||||
}
|
||||
}
|
||||
?>
|
||||
101
services/www/api/objects/like.php
Normal file
101
services/www/api/objects/like.php
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
65
services/www/api/objects/presence.php
Normal file
65
services/www/api/objects/presence.php
Normal 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;
|
||||
|
||||
}
|
||||
}
|
||||
?>
|
||||
43
services/www/api/objects/token.php
Normal file
43
services/www/api/objects/token.php
Normal 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
62
services/www/api/objects/user.php
Normal file
62
services/www/api/objects/user.php
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
42
services/www/api/presence/create.php
Normal file
42
services/www/api/presence/create.php
Normal 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."));
|
||||
}
|
||||
|
||||
?>
|
||||
51
services/www/api/presence/read.php
Normal file
51
services/www/api/presence/read.php
Normal 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);
|
||||
71
services/www/api/table/get.php
Normal file
71
services/www/api/table/get.php
Normal 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);
|
||||
61
services/www/api/token/create.php
Normal file
61
services/www/api/token/create.php
Normal 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
1
services/www/config/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
database.php
|
||||
25
services/www/config/database_example.php
Normal file
25
services/www/config/database_example.php
Normal 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
1947
services/www/php.ini
Normal file
File diff suppressed because it is too large
Load Diff
77
services/www/report.php
Normal file
77
services/www/report.php
Normal 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>
|
||||
20
services/www/static/attendee-gift.json
Normal file
20
services/www/static/attendee-gift.json
Normal 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
|
||||
}
|
||||
20
services/www/static/food-menu.json
Normal file
20
services/www/static/food-menu.json
Normal 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
|
||||
}
|
||||
32
services/www/static/location.json
Normal file
32
services/www/static/location.json
Normal 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
|
||||
}
|
||||
BIN
services/www/static/media/attendee-gift.jpg
Normal file
BIN
services/www/static/media/attendee-gift.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 714 KiB |
BIN
services/www/static/media/food-menu.jpg
Normal file
BIN
services/www/static/media/food-menu.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
BIN
services/www/static/media/location.jpg
Normal file
BIN
services/www/static/media/location.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 180 KiB |
BIN
services/www/static/media/wedding-gift.jpg
Normal file
BIN
services/www/static/media/wedding-gift.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
184
services/www/static/places.json
Normal file
184
services/www/static/places.json
Normal 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
|
||||
}
|
||||
19
services/www/static/settings.json
Normal file
19
services/www/static/settings.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
20
services/www/static/wedding-gift.json
Normal file
20
services/www/static/wedding-gift.json
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user