Reorganised RN app

pull/41/head
Laurent Cozic 2017-05-07 22:02:17 +01:00
parent 037537db52
commit e5d7294b69
13 changed files with 354 additions and 120 deletions

View File

@ -129,6 +129,7 @@ dependencies {
compile fileTree(dir: "libs", include: ["*.jar"])
compile "com.android.support:appcompat-v7:23.0.1"
compile "com.facebook.react:react-native:+" // From node_modules
compile project(':react-native-sqlite-storage')
}
// Run this once to be able to run the application with BUCK

View File

@ -7,6 +7,7 @@ import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;
import org.pgsqlite.SQLitePluginPackage;
import java.util.Arrays;
import java.util.List;
@ -22,6 +23,7 @@ public class MainApplication extends Application implements ReactApplication {
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new SQLitePluginPackage(),
new MainReactPackage()
);
}

View File

@ -1,3 +1,6 @@
rootProject.name = 'AwesomeProject'
include ':app'
include ':react-native-sqlite-storage'
project(':react-native-sqlite-storage').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sqlite-storage/src/android')

View File

@ -5,6 +5,27 @@ import { connect } from 'react-redux'
import { createStore } from 'redux';
import { Provider } from 'react-redux'
import { WebApi } from 'src/web-api.js'
import { Database } from 'src/database.js'
import { Log } from 'src/log.js'
let debugMode = true;
let db = new Database();
db.setDebugEnabled(debugMode);
db.open();
// let test = {
// 'abcd' : 123,
// 'efgh' : 456,
// }
// for (let [key, value] of test) {
// console.info(key, value);
// }
let defaultState = {
'myButtonLabel': 'clicko123456',
'counter': 0,
@ -108,90 +129,18 @@ class App extends Component {
}
const queryString = require('query-string');
class Api {
constructor(baseUrl, clientId) {
this.baseUrl_ = baseUrl;
this.clientId_ = clientId;
}
makeRequest(method, path, query, data) {
let url = this.baseUrl_;
if (path) url += '/' + path;
if (query) url += '?' + queryString(query);
let options = {};
options.method = method.toUpperCase();
if (data) {
var formData = new FormData();
for (var key in data) {
if (!data.hasOwnProperty(key)) continue;
formData.append(key, data[key]);
}
options.body = formData;
}
return {
url: url,
options: options
};
}
exec(method, path, query, data) {
let that = this;
return new Promise(function(resolve, reject) {
let r = that.makeRequest(method, path, query, data);
fetch(r.url, r.options)
.then(function(response) {
let responseClone = response.clone();
return response.json()
.then(function(data) {
resolve(data);
})
.catch(function(error) {
responseClone.text()
.done(function(text) {
reject('Cannot parse JSON: ' + text);
});
});
})
.then(function(data) {
resolve(data);
})
.catch(function(error) {
reject(error);
});
});
}
get(path, query) {
return this.exec('GET', path, query);
}
post(path, query, data) {
return this.exec('POST', path, query, data);
}
delete(path, query) {
return this.exec('DELETE', path, query);
}
}
let api = new Api('http://192.168.1.2', 'A7D301DA7D301DA7D301DA7D301DA7D3');
api.exec('POST', 'sessions', null, {
'email': 'laurent@cozic.net',
'password': '12345678',
})
.then(function(data) {
console.info('GOT DATA:');
console.info(data);
})
.catch(function(error) {
console.warn('GOT ERROR:');
console.warn(error);
})
// let api = new WebApi('http://192.168.1.2', 'A7D301DA7D301DA7D301DA7D301DA7D3');
// api.exec('POST', 'sessions', null, {
// 'email': 'laurent@cozic.net',
// 'password': '12345678',
// })
// .then(function(data) {
// console.info('GOT DATA:');
// console.info(data);
// })
// .catch(function(error) {
// console.warn('GOT ERROR:');
// console.warn(error);
// })
AppRegistry.registerComponent('AwesomeProject', () => App);

View File

@ -17,7 +17,8 @@
"react-test-renderer": "16.0.0-alpha.6",
"redux": "3.6.0",
"react-redux": "4.4.8",
"query-string": "4.3.4"
"query-string": "4.3.4",
"react-native-sqlite-storage": "3.3.*"
},
"jest": {
"preset": "react-native"

View File

@ -0,0 +1,155 @@
import SQLite from 'react-native-sqlite-storage';
import { Log } from 'src/log.js';
const structureSql = `
CREATE TABLE folders (
id TEXT PRIMARY KEY,
parent_id TEXT NOT NULL DEFAULT "",
title TEXT NOT NULL DEFAULT "",
created_time INT NOT NULL DEFAULT 0,
updated_time INT NOT NULL DEFAULT 0
);
CREATE TABLE notes (
id TEXT PRIMARY KEY,
parent_id TEXT NOT NULL DEFAULT "",
title TEXT NOT NULL DEFAULT "",
body TEXT NOT NULL DEFAULT "",
created_time INT NOT NULL DEFAULT 0,
updated_time INT NOT NULL DEFAULT 0,
latitude NUMERIC NOT NULL DEFAULT 0,
longitude NUMERIC NOT NULL DEFAULT 0,
altitude NUMERIC NOT NULL DEFAULT 0,
source TEXT NOT NULL DEFAULT "",
author TEXT NOT NULL DEFAULT "",
source_url TEXT NOT NULL DEFAULT "",
is_todo BOOLEAN NOT NULL DEFAULT 0,
todo_due INT NOT NULL DEFAULT "",
todo_completed INT NOT NULL DEFAULT "",
source_application TEXT NOT NULL DEFAULT "",
application_data TEXT NOT NULL DEFAULT "",
\`order\` INT NOT NULL DEFAULT 0
);
CREATE TABLE tags (
id TEXT PRIMARY KEY,
title TEXT,
created_time INT,
updated_time INT
);
CREATE TABLE note_tags (
id INTEGER PRIMARY KEY,
note_id TEXT,
tag_id TEXT
);
CREATE TABLE resources (
id TEXT PRIMARY KEY,
title TEXT,
mime TEXT,
filename TEXT,
created_time INT,
updated_time INT
);
CREATE TABLE note_resources (
id INTEGER PRIMARY KEY,
note_id TEXT,
resource_id TEXT
);
CREATE TABLE version (
version INT
);
CREATE TABLE changes (
id INTEGER PRIMARY KEY,
\`type\` INT,
item_id TEXT,
item_type INT,
item_field TEXT
);
CREATE TABLE settings (
\`key\` TEXT PRIMARY KEY,
\`value\` TEXT,
\`type\` INT
);
INSERT INTO version (version) VALUES (1);
`;
class Database {
constructor() {}
setDebugEnabled(v) {
SQLite.DEBUG(v);
}
open() {
this.db_ = SQLite.openDatabase({ name: 'joplin.sqlite', location: 'Documents' }, (db) => {
Log.info('Database was open successfully');
}, (error) => {
Log.error('Cannot open database: ', error);
});
this.updateSchema();
}
sqlStringToLines(sql) {
let output = [];
let lines = sql.split("\n");
let statement = '';
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line == '') continue;
if (line.substr(0, 2) == "--") continue;
statement += line;
if (line[line.length - 1] == ';') {
output.push(statement);
statement = '';
}
}
return output;
}
selectOne(sql, params = null) {
return new Promise((resolve, reject) => {
this.db_.executeSql(sql, params, (r) => {
resolve(r.rows.length ? r.rows.item(0) : null);
}, (error) => {
reject(error);
});
});
}
updateSchema() {
Log.info('Checking for database schema update...');
this.selectOne('SELECT * FROM version LIMIT 1').then((row) => {
// TODO: version update logic
}).catch((error) => {
// Assume that error was:
// { message: 'no such table: version (code 1): , while compiling: SELECT * FROM version', code: 0 }
// which means the database is empty and the tables need to be created.
Log.info('Database is new - creating the schema...');
let statements = this.sqlStringToLines(structureSql)
this.db_.transaction((tx) => {
for (let i = 0; i < statements.length; i++) {
tx.executeSql(statements[i]);
}
}, (error) => {
Log.error('Could not create database schema:', error);
}, () => {
Log.info('Database schema created successfully');
});
});
}
}
export { Database };

View File

@ -0,0 +1,19 @@
// Custom wrapper for `console` to allow for custom logging (to file, etc.) if needed.
class Log {
static info(...o) {
console.info(...o);
}
static warn(...o) {
console.info(...o);
}
static error(...o) {
console.info(...o);
}
}
export { Log };

View File

@ -0,0 +1 @@
{ "name": "src" }

View File

@ -0,0 +1,70 @@
const queryString = require('query-string');
class WebApi {
constructor(baseUrl, clientId) {
this.baseUrl_ = baseUrl;
this.clientId_ = clientId;
}
makeRequest(method, path, query, data) {
let url = this.baseUrl_;
if (path) url += '/' + path;
if (query) url += '?' + queryString(query);
let options = {};
options.method = method.toUpperCase();
if (data) {
var formData = new FormData();
for (var key in data) {
if (!data.hasOwnProperty(key)) continue;
formData.append(key, data[key]);
}
options.body = formData;
}
return {
url: url,
options: options
};
}
exec(method, path, query, data) {
let that = this;
return new Promise(function(resolve, reject) {
let r = that.makeRequest(method, path, query, data);
fetch(r.url, r.options).then(function(response) {
let responseClone = response.clone();
return response.json().then(function(data) {
resolve(data);
})
.catch(function(error) {
responseClone.text().then(function(text) {
reject('Cannot parse JSON: ' + text);
});
});
})
.then(function(data) {
resolve(data);
})
.catch(function(error) {
reject(error);
});
});
}
get(path, query) {
return this.exec('GET', path, query);
}
post(path, query, data) {
return this.exec('POST', path, query, data);
}
delete(path, query) {
return this.exec('DELETE', path, query);
}
}
export { WebApi };

View File

@ -5,33 +5,38 @@
"path": ".",
"folder_exclude_patterns": [
"var",
"vendor",
"QtClient/build-JoplinQtClient-Visual_C_32_bits-Debug",
"QtClient/data/resources",
"app/data/uploads",
"ReactNativeClient/node_modules",
"ReactNativeClient/android",
"ReactNativeClient/ios",
"vendor",
"QtClient/build-JoplinQtClient-Visual_C_32_bits-Debug",
"QtClient/data/resources",
"app/data/uploads",
"ReactNativeClient/node_modules",
"ReactNativeClient/android/app/build",
"ReactNativeClient/android/build",
"ReactNativeClient/android/.idea",
"ReactNativeClient/android/.gradle",
"ReactNativeClient/android/local.properties",
"ReactNativeClient/ios"
],
"file_exclude_patterns": [
"*.pro.user",
"*.pro.user.*"
]
"file_exclude_patterns": [
"*.pro.user",
"*.pro.user.*",
"*.iml"
]
}
],
"build_systems":
[
{
"name": "Build evernote-import",
"shell_cmd": "D:\\Programmes\\cygwin\\bin\\bash.exe --login D:\\Web\\www\\joplin\\QtClient\\evernote-import\\build.sh"
},
{
"name": "Qt Creator - Build and run",
"shell_cmd": "D:\\NonPortableApps\\AutoHotkey\\AutoHotkey.exe D:\\Docs\\PROGS\\AutoHotKey\\QtRun\\QtRun.ahk"
},
{
"name": "Build QtClient CLI - Linux",
"shell_cmd": "/home/laurent/src/notes/QtClient/JoplinQtClient/build.sh"
}
]
"build_systems":
[
{
"name": "Build evernote-import",
"shell_cmd": "D:\\Programmes\\cygwin\\bin\\bash.exe --login D:\\Web\\www\\joplin\\QtClient\\evernote-import\\build.sh"
},
{
"name": "Qt Creator - Build and run",
"shell_cmd": "D:\\NonPortableApps\\AutoHotkey\\AutoHotkey.exe D:\\Docs\\PROGS\\AutoHotKey\\QtRun\\QtRun.ahk"
},
{
"name": "Build QtClient CLI - Linux",
"shell_cmd": "/home/laurent/src/notes/QtClient/JoplinQtClient/build.sh"
}
]
}

View File

@ -32,12 +32,24 @@ abstract class ApiController extends Controller {
$r->send();
echo "\n";
} else {
$msg = array();
$msg[] = 'Exception: ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine();
$msg[] = '';
$msg[] = $e->getTraceAsString();
echo implode("\n", $msg);
$r = array(
'error' => $e->getMessage(),
'code' => 0,
'type' => 'Exception',
//'trace' => $e->getTraceAsString(),
);
$response = new JsonResponse($r);
$response->setStatusCode(500);
$response->send();
echo "\n";
// $msg = array();
// $msg[] = 'Exception: ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine();
// $msg[] = '';
// $msg[] = $e->getTraceAsString();
// echo implode("\n", $msg);
// echo "\n";
}
});

View File

@ -29,6 +29,8 @@ class Session extends BaseModel {
$ok = self::verifyPassword($password, $user->password);
if (!$ok) throw new AuthException();
if (!$clientId) throw new \Exception('clientId is required');
$session = new Session();
$session->owner_id = $user->id;
$session->client_id = $clientId;

View File

@ -21,7 +21,21 @@ try {
$response->send();
$kernel->terminate($request, $response);
} catch(\Exception $e) {
header('HTTP/1.1 500 Internal Server Error');
echo $e->getMessage() . "\n";
echo $e->getTraceAsString();
// Separate exception handling for anything that could not be caught in ApiController, for
// example if the route doesn't exist.
$class = get_class($e);
$errorType = explode("\\", $class);
$errorType = $errorType[count($errorType) - 1];
$response = array(
'error' => $e->getMessage(),
'code' => $e->getCode(),
'type' => $errorType,
);
if ($errorType == 'NotFoundHttpException') {
header('HTTP/1.1 404 Not found');
} else {
header('HTTP/1.1 500 Internal Server Error');
}
die(json_encode($response) . "\n");
}