implemented busineslayer methods

This commit is contained in:
Bernhard Posselt 2013-04-03 17:50:29 +02:00
parent 0f3ad504ff
commit 1c4ae20dad
14 changed files with 802 additions and 34 deletions

View File

@ -1,13 +1,36 @@
<?php
$l=OC_L10N::get('notes');
/**
* Copyright (c) 2013, Bernhard Posselt <nukeawhale@gmail.com>
* This file is licensed under the Affero General Public License version 3 or later.
* See the COPYING file.
*/
OC::$CLASSPATH['OCA\Notes\Notes'] = 'notes/lib/notes.php';
OC::$CLASSPATH['OCA\Notes\Categories'] = 'notes/lib/categories.php';
namespace OCA\Notes;
use \OCA\AppFramework\Core\API;
$api = new API('notes');
$api->addNavigationEntry(array(
// the string under which your app will be referenced in owncloud
'id' => $api->getAppName(),
// sorting weight for the navigation. The higher the number, the higher
// will it be listed in the navigation
'order' => 10,
// the route that will be shown on startup
'href' => $api->linkToRoute('notes_index'),
// the icon that will be shown in the navigation
// this file needs to exist in img/example.png
'icon' => $api->imagePath('notes.svg'),
// the title of your application. This will be used in the
// navigation or on the settings page of your app
'name' => $api->getTrans()->t('Notes')
));
OCP\App::addNavigationEntry( array(
'id' => 'notes_index',
'order' => 11,
'href' => OCP\Util::linkTo( 'notes', 'index.php' ),
'icon' => OCP\Util::imagePath( 'notes', 'notes.svg' ),
'name' => $l->t('Notes'))
);

46
appinfo/routes.php Normal file
View File

@ -0,0 +1,46 @@
<?php
/**
* Copyright (c) 2013, Bernhard Posselt <nukeawhale@gmail.com>
* This file is licensed under the Affero General Public License version 3 or later.
* See the COPYING file.
*/
namespace OCA\Notes;
use \OCA\AppFramework\App;
use \OCA\Notes\DependencyInjection\DIContainer;
/**
* Webinterface
*/
// matches /owncloud/index.php/apps/notes/
$this->create('notes_index', '/')->get()->action(
function($params){
App::main('PageController', 'index', $params, new DIContainer());
}
);
/**
* Ajax requests
*/
$this->create('notes_save', '/notes')->get()->action(
function($params){
App::main('NotesController', 'getAll', $params, new DIContainer());
}
);
$this->create('notes_save', '/note')->get()->action(
function($params){
App::main('NotesController', 'get', $params, new DIContainer());
}
);
$this->create('notes_save', '/note/save')->post()->action(
function($params){
App::main('NotesController', 'save', $params, new DIContainer());
}
);

View File

@ -0,0 +1,87 @@
<?php
/**
* Copyright (c) 2013, Bernhard Posselt <nukeawhale@gmail.com>
* This file is licensed under the Affero General Public License version 3 or later.
* See the COPYING file.
*/
namespace OCA\Notes\BusinessLayer;
use \OCA\Notes\Db\Note;
class NotesBusinessLayer {
private $fileSystem;
public function __construct($fileSystem) {
$this->fileSystem = $fileSystem;
}
public function getAllNotes(){
$files = $this->fileSystem->getDirectoryContent('/');
$notes = array();
foreach($files as $file) {
if($file['type'] === 'file') {
$file['content'] = ''; // no content because this is fast ;)
$note = new Note();
$note->fromFile($file);
array_push($notes, $note);
}
}
return $notes;
}
public function getNote($title) {
// prevent directory traversal
$title = str_replace(array('/', '\\'), '', $title);
$note = new Note();
$note->fromFile(array(
'name' => $title,
'content' => $this->fileSystem->file_get_contents('/' . $title . '.txt'),
'mtime' => $this->fileSystem->filemtime('/' . $title . '.txt')
));
return $note;
}
/**
* If the file exists, rename the file, otherwise create a new file
*/
public function saveNote($oldTitle, $newTitle, $content){
// prevent directory traversal
$oldTitle = str_replace(array('/', '\\'), '', $oldTitle);
$newTitle= str_replace(array('/', '\\'), '', $newTitle);
// rename if the old file exists
if($this->fileSystem->file_exists('/' . $oldTitle . '.txt')){
$this->fileSystem->rename('/' . $oldTitle . '.txt', '/' .
$newTitle . '.txt');
}
// in any case save the content
$this->fileSystem->file_put_contents('/' . $newTitle . '.txt',
$content);
}
public function deleteNote($title) {
// prevent directory traversal
$title = str_replace(array('/', '\\'), '', $title);
$this->fileSystem->unlink('/' . $title . '.txt');
}
}

View File

@ -0,0 +1,95 @@
<?php
/**
* Copyright (c) 2013, Bernhard Posselt <nukeawhale@gmail.com>
* This file is licensed under the Affero General Public License version 3 or later.
* See the COPYING file.
*/
namespace OCA\Notes\Controller;
use \OCA\AppFramework\Controller\Controller;
use \OCA\AppFramework\Core\API;
use \OCA\AppFramework\Http\Request;
use \OCA\Notes\BusinessLayer\NotesBusinessLayer;
class NotesController extends Controller {
private $businessLayer;
public function __construct(API $api, Request $request,
NotesBusinessLayer $notesBizLayer){
parent::__construct($api, $request);
$this->businessLayer = $notesBizLayer;
}
/**
* ATTENTION!!!
* The following comments are needed "here" but turn off security checks
* Please look up their meaning in the documentation:
* http://doc.owncloud.org/server/master/developer_manual/app/appframework/controllers.html
*
* @IsAdminExemption
* @IsSubAdminExemption
* @Ajax
*/
public function getAll() {
$notes = $this->businessLayer->getAllNotes();
$params = array(
'notes' => $notes
);
return $this->renderJSON($params);
}
/**
* @IsAdminExemption
* @IsSubAdminExemption
* @Ajax
*/
public function get() {
$title = $this->params('title');
$note = $this->businessLayer->getNote($title);
$params = array(
'notes' => array($note)
);
return $this->renderJSON($params);
}
/**
* @IsAdminExemption
* @IsSubAdminExemption
* @Ajax
*/
public function save() {
$oldTitle = $this->params('oldTitle');
$newTitle = $this->params('newTitle');
$content = $this->params('content');
$this->businessLayer->saveNote($oldTitle, $newTitle, $content);
return $this->renderJSON();
}
/**
* @IsAdminExemption
* @IsSubAdminExemption
* @Ajax
*/
public function delete() {
$title = $this->params('title');
$this->businessLayer->deleteNote($title);
return $this->renderJSON();
}
}

View File

@ -0,0 +1,38 @@
<?php
/**
* Copyright (c) 2013, Bernhard Posselt <nukeawhale@gmail.com>
* This file is licensed under the Affero General Public License version 3 or later.
* See the COPYING file.
*/
namespace OCA\Notes\Controller;
use \OCA\AppFramework\Controller\Controller;
use \OCA\AppFramework\Core\API;
use \OCA\AppFramework\Http\Request;
class PageController extends Controller {
public function __construct(API $api, Request $request){
parent::__construct($api, $request);
}
/**
* ATTENTION!!!
* The following comments turn off security checks
* Please look up their meaning in the documentation:
* http://doc.owncloud.org/server/master/developer_manual/app/appframework/controllers.html
*
* @IsAdminExemption
* @IsSubAdminExemption
* @CSRFExemption
*/
public function index() {
return $this->render('main');
}
}

27
db/note.php Normal file
View File

@ -0,0 +1,27 @@
<?php
/**
* Copyright (c) 2013, Bernhard Posselt <nukeawhale@gmail.com>
* This file is licensed under the Affero General Public License version 3 or later.
* See the COPYING file.
*/
namespace OCA\Notes\Db;
class Note {
public $modified;
public $title;
public $content;
public function fromFile($file){
$this->modified = (int) $file['mtime'];
$this->title = $file['name'];
$this->content = $file['content'];
}
public function getModified(){ return $this->modified; }
public function getTitle(){ return $this->title; }
public function getContent(){ return $this->content; }
}

View File

@ -0,0 +1,70 @@
<?php
/**
* Copyright (c) 2013, Bernhard Posselt <nukeawhale@gmail.com>
* This file is licensed under the Affero General Public License version 3 or later.
* See the COPYING file.
*/
namespace OCA\Notes\DependencyInjection;
use \OC\Files\View;
use \OCA\AppFramework\DependencyInjection\DIContainer as BaseContainer;
use \OCA\Notes\Controller\PageController;
use \OCA\Notes\Controller\NotesController;
use \OCA\Notes\BusinessLayer\NotesBusinessLayer;
class DIContainer extends BaseContainer {
/**
* Define your dependencies in here
*/
public function __construct(){
// tell parent container about the app name
parent::__construct('notes');
/**
* CONTROLLERS
*/
$this['PageController'] = $this->share(function($c){
return new PageController($c['API'], $c['Request']);
});
$this['NotesController'] = $this->share(function($c){
return new NotesController($c['API'], $c['Request'],
$c['NotesBusinessLayer']);
});
/**
* Business Layer
*/
$this['NotesBusinessLayer'] = $this->share(function($c){
return new NotesBusinessLayer($c['FileSystem']);
});
/**
* Utilities
*/
$this['FileSystem'] = $this->share(function($c){
$userName = $c['API']->getUserId();
$view = new View('/' . $userName . '/files/Notes');
if (!$this->view->file_exists('/')) {
$this->view->mkdir('/');
}
return $view;
});
}
}

View File

@ -1,23 +0,0 @@
<?php
/**
* Copyright (c) 2013 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OCA\Notes;
\OCP\User::checkLoggedIn();
\OCP\App::checkAppEnabled('notes');
//\OCP\Util::addscript('notes', 'notes');
\OCP\App::setActiveNavigationEntry('notes_index');
$categories = new Categories(\OCP\User::getUser());
$notes = new Notes(\OCP\User::getUser());
$tmpl = new \OCP\Template('notes', 'notes', 'user');
$tmpl->assign('categories', $categories->listCategories());
$tmpl->assign('notes', $notes->getTitles(''));
$tmpl->printPage();

View File

@ -1,6 +1,6 @@
# Copyright (c) 2013, Bernhard Posselt <nukeawhale@gmail.com>
# This file is licensed under the Affero General Public License version 3 or later.
# See the COPYING-README file.
# See the COPYING file.
firefox_bin=/usr/bin/firefox
chrome_bin=/usr/bin/chromium

36
templates/main.php Normal file
View File

@ -0,0 +1,36 @@
<?php
\OCP\Util::addScript('appframework', 'vendor/angular/angular');
\OCP\Util::addScript('appframework', 'public/app');
\OCP\Util::addScript('notes', 'public/app');
\OCP\Util::addStyle('notes', 'notes');
?>
<div id="app" ng-app="Notes" ng-controller="NotesController">
<div id="app-navigation">
<ul>
<li id="note-add" ng-click="createNew()"
oc-click-focus="{selector: '#app-content textarea'}">
<a href='#'>+ <span><?php p($l->t('New Note')); ?></span></a>
</li>
<li ng-repeat="note in notes|orderBy:'modified':'reverse'"
ng-show="note.content"
ng-class="{active: note==activeNote}">
<a href="#" ng-click="load(note)">{{ note.title }}</a>
</li>
</ul>
</div>
<div id="app-content"
ng-class="{loading: loading.isLoading()}">
<textarea ng-change="update(activeNote)"
ng-model="activeNote.content"
ng-hide="loading.isLoading()"
autofocus></textarea>
</div>
</div>

View File

@ -0,0 +1,155 @@
<?php
/**
* Copyright (c) 2013, Bernhard Posselt <nukeawhale@gmail.com>
* This file is licensed under the Affero General Public License version 3 or later.
* See the COPYING file.
*/
namespace OCA\Notes\BusinessLayer;
use \OCA\AppFramework\Http\Request;
use \OCA\AppFramework\Http\JSONResponse;
use \OCA\AppFramework\Utility\TestUtility;
use \OCA\Notes\Db\Note;
require_once(__DIR__ . "/../classloader.php");
class NotesBusinessLayerTest extends TestUtility {
private $fileSystem;
private $filesystemNotes;
private $notes;
public function setUp(){
$this->fileSystem = $this->getMock('Filesystem',
array(
'getDirectoryContent',
'unlink',
'file_get_contents',
'filemtime',
'file_exists',
'rename',
'file_put_contents'
));
$this->bizLayer = new NotesBusinessLayer($this->fileSystem);
// reusable test data
$this->filesystemNotes = array(
array(
'type' => 'file',
'mtime' => 50,
'name' => 'hi.txt',
'content' => ''
),
array(
'type' => 'directory',
'mtime' => 50,
'name' => ''
),
array(
'type' => 'file',
'mtime' => 502,
'name' => 'yo.txt',
'content' => ''
)
);
$note1 = new Note();
$note1->fromFile($this->filesystemNotes[0]);
$note2 = new Note();
$note2->fromFile($this->filesystemNotes[2]);
$this->notes = array(
$note1,
$note2
);
}
public function testGetAllNotes(){
$this->fileSystem->expects($this->once())
->method('getDirectoryContent')
->with($this->equalTo('/'))
->will($this->returnValue($this->filesystemNotes));
$result = $this->bizLayer->getAllNotes();
$this->assertEquals($this->notes[0], $result[0]);
$this->assertEquals($this->notes[1], $result[1]);
$this->assertCount(2, $result);
}
public function testRemove(){
$title = 'hi';
$this->fileSystem->expects($this->once())
->method('unlink')
->with($this->equalTo('/' . $title . '.txt' ))
->will($this->returnValue($this->filesystemNotes));
$this->bizLayer->deleteNote($title);
}
public function testGetNote(){
$expected = new Note();
$expected->fromFile(
$this->filesystemNotes[0]
);
$title = $expected->getTitle();
$this->fileSystem->expects($this->once())
->method('file_get_contents')
->with($this->equalTo('/' . $title . '.txt' ))
->will($this->returnValue($this->filesystemNotes[0]['content']));
$this->fileSystem->expects($this->once())
->method('filemtime')
->with($this->equalTo('/' . $title . '.txt' ))
->will($this->returnValue($this->filesystemNotes[0]['mtime']));
$result = $this->bizLayer->getNote($title);
$this->assertEquals($expected, $result);
}
public function testSaveNoteRenamesNoteWhenTitleChanged(){
$newTitle = 'heho';
$title = $this->filesystemNotes[0]['name'];
$content = 'content';
$this->fileSystem->expects($this->once())
->method('file_exists')
->with($this->equalTo('/' . $title . '.txt'))
->will($this->returnValue(true));
$this->fileSystem->expects($this->once())
->method('rename')
->with($this->equalTo('/' . $title . '.txt'),
$this->equalTo('/' . $newTitle . '.txt'));
$result = $this->bizLayer->saveNote($title, $newTitle, $content);
}
public function testSaveNoteCreatesAndDoesNotRenameWhenTitleSametleChanged(){
$newTitle = 'heho';
$title = $this->filesystemNotes[0]['name'];
$content = 'content';
$this->fileSystem->expects($this->once())
->method('file_exists')
->with($this->equalTo('/' . $title . '.txt'))
->will($this->returnValue(false));
$this->fileSystem->expects($this->never())
->method('rename');
$this->fileSystem->expects($this->once())
->method('file_put_contents')
->with($this->equalTo('/' . $newTitle . '.txt'),
$this->equalTo($content));
$result = $this->bizLayer->saveNote($title, $newTitle, $content);
}
}

19
tests/classloader.php Normal file
View File

@ -0,0 +1,19 @@
<?php
/**
* Copyright (c) 2013, Bernhard Posselt <nukeawhale@gmail.com>
* This file is licensed under the Affero General Public License version 3 or later.
* See the COPYING file.
*/
// to execute without owncloud, we need to create our own classloader
spl_autoload_register(function ($className){
if (strpos($className, 'OCA\\') === 0) {
$path = strtolower(str_replace('\\', '/', substr($className, 3)) . '.php');
$relPath = __DIR__ . '/../..' . $path;
if(file_exists($relPath)){
require_once $relPath;
}
}
});

View File

@ -0,0 +1,157 @@
<?php
/**
* Copyright (c) 2013, Bernhard Posselt <nukeawhale@gmail.com>
* This file is licensed under the Affero General Public License version 3 or later.
* See the COPYING file.
*/
namespace OCA\Notes\Controller;
use \OCA\AppFramework\Http\Request;
use \OCA\AppFramework\Http\JSONResponse;
use \OCA\AppFramework\Utility\ControllerTestUtility;
require_once(__DIR__ . "/../classloader.php");
class NotesControllerTest extends ControllerTestUtility {
private $api;
private $request;
private $controller;
private $bizLayer;
/**
* Gets run before each test
*/
public function setUp(){
$this->api = $this->getAPIMock();
$this->bizLayer = $this->getMockBuilder(
'\OCA\Notes\BusinessLayer\NotesBusinessLayer')
->disableOriginalConstructor()
->getMock();
$this->request = new Request();
$this->controller =
new NotesController($this->api, $this->request, $this->bizLayer);
}
public function testGetAllAnnotations(){
$annotations = array('IsAdminExemption', 'IsSubAdminExemption', 'Ajax');
$this->assertAnnotations($this->controller, 'getAll', $annotations);
}
public function testGetAllReturnsJSON(){
$result = $this->controller->getAll();
$this->assertTrue($result instanceof JSONResponse);
}
public function testCallsNotesBizLayer(){
$expected = array(
'notes' => array('hi')
);
$this->bizLayer->expects($this->once())
->method('getAllNotes')
->will($this->returnValue($expected['notes']));
$result = $this->controller->getAll();
$params = $result->getParams();
$this->assertEquals($expected, $params);
}
public function testGetAnnotations(){
$annotations = array('IsAdminExemption', 'IsSubAdminExemption', 'Ajax');
$this->assertAnnotations($this->controller, 'get', $annotations);
}
public function testGetReturnsJSON(){
$result = $this->controller->get();
$this->assertTrue($result instanceof JSONResponse);
}
public function testGetCallsBizLayer(){
$expected = array(
'notes' => array('hi')
);
$getParams = array('title' => 'tests');
$request = new Request($getParams);
$this->controller = new NotesController($this->api, $request,
$this->bizLayer);
$this->bizLayer->expects($this->once())
->method('getNote')
->with($this->equalTo($getParams['title']))
->will($this->returnValue($expected['notes'][0]));
$result = $this->controller->get();
$params = $result->getParams();
$this->assertEquals($expected, $params);
}
public function testSaveAnnotations(){
$annotations = array('IsAdminExemption', 'IsSubAdminExemption', 'Ajax');
$this->assertAnnotations($this->controller, 'save', $annotations);
}
public function testSaveReturnsJSON(){
$result = $this->controller->save();
$this->assertTrue($result instanceof JSONResponse);
}
public function testSaveCallsBizLayer(){
$postParams = array(
'oldTitle' => 'tests',
'newTitle' => 'tests2',
'content' => 'cont'
);
$request = new Request($postParams);
$this->controller = new NotesController($this->api, $request,
$this->bizLayer);
$this->bizLayer->expects($this->once())
->method('saveNote')
->with($this->equalTo($postParams['oldTitle']),
$this->equalTo($postParams['newTitle']),
$this->equalTo($postParams['content']));
$result = $this->controller->save();
}
public function testDeleteAnnotations(){
$annotations = array('IsAdminExemption', 'IsSubAdminExemption', 'Ajax');
$this->assertAnnotations($this->controller, 'delete', $annotations);
}
public function testDeleteReturnsJSON(){
$result = $this->controller->delete();
$this->assertTrue($result instanceof JSONResponse);
}
public function testDeleteCallsBizLayer(){
$postParams = array(
'title' => 'tests'
);
$request = new Request($postParams);
$this->controller = new NotesController($this->api, $request,
$this->bizLayer);
$this->bizLayer->expects($this->once())
->method('deleteNote')
->with($this->equalTo($postParams['title']));
$result = $this->controller->delete();
}
}

38
tests/db/NoteTest.php Normal file
View File

@ -0,0 +1,38 @@
<?php
/**
* Copyright (c) 2013, Bernhard Posselt <nukeawhale@gmail.com>
* This file is licensed under the Affero General Public License version 3 or later.
* See the COPYING file.
*/
namespace OCA\Notes\Db;
use \OCA\AppFramework\Http\Request;
use \OCA\AppFramework\Http\JSONResponse;
use \OCA\AppFramework\Utility\TestUtility;
require_once(__DIR__ . "/../classloader.php");
class NoteTest extends TestUtility {
public function testFromFile(){
$file = array(
'type' => 'file',
'mtime' => 50,
'name' => 'hi.txt',
'content' => 'hehe'
);
$note = new Note();
$note->fromFile($file);
$this->assertEquals($file['mtime'], $note->getModified());
$this->assertEquals($file['name'], $note->getTitle());
$this->assertEquals($file['content'], $note->getContent());
}
}