얼마 전 ThingsDB라는 데이터베이스를 발견했습니다. 궁금해서 좀 읽어봤습니다. TCP 연결을 지원하지만 일부 특정 플랫폼용 드라이버가 없다는 사실을 발견했습니다. 따라서 javascript 및 php용 드라이버를 개발했습니다.
Javascript 드라이버를 작업하면서 깨달았습니다. 백엔드나 미들웨어 없이 프런트엔드에서 직접 ThingsDB를 사용할 수 있다는 점이었습니다. 브라우저에서 웹소켓(TCP) 연결을 열 수 있으므로 ThingsDB 작성자에게 연락했더니 웹소켓에 대한 지원이 추가되었습니다(ThingsDB 버전 1.6부터 사용 가능). 이렇게 하면 내 자바스크립트 드라이버를 프런트엔드(브라우저)와 자바스크립트 기반 백엔드(예: node.js)에서 사용할 수 있습니다. 나는 흥미로운 피드백을 받은 PHP 드라이버에 관한 기사를 여기에 썼습니다. 사람들은 ThingsDB의 잠재력을 더 많이 보고 싶어했습니다. 이에 따라 나는 자바스크립트 드라이버에 대한 기사를 마친 후 바로 쓰지 않기로 결정했지만 데모를 만드는 것이 최선이라고 결정했습니다.
ThingsDB와 이 데모의 기본 사항을 이해하려면 구체적인 기능을 설명하는 동안 계속해서 읽어 보시기 바랍니다. 당신은 최소한 기본적인 프로그래밍 전반에 대해 잘 알고 있을 것으로 기대합니다. 그리고 자바스크립트와 jQuery도 있을 수 있습니다.
ThingsDB 내부에서 코드 스니펫을 실행하여 이 글을 따라가려면 설치 가이드에 언급된 첨부된 docker 파일을 사용해야 합니다.
먼저 중요한 일을 먼저 하세요. 간단히 구조를 설명하겠습니다.
ThingsDB에는 컬렉션이 포함되어 있습니다. 컬렉션에는 데이터, 프로시저, 작업, 데이터 유형 및 열거형이 포함됩니다. 사용자 액세스 계정을 포함하고 프로시저와 작업도 포함할 수 있는 사전 컬렉션(범위) @thingsdb도 있습니다. 마지막으로 지금은 중요하지 않은 @node 범위가 있습니다.
데이터, 프로시저, 작업, 데이터 유형, 열거형 등 명명된 모든 항목은 ThingsDB를 구현하는 개발자가 정의합니다. 이 데이터베이스의 새 인스턴스에는 @:stuff라는 빈 컬렉션과 사용자 계정 admin만 포함되어 있습니다. 저는 이 컬렉션을 이번 데모의 주요 컬렉션으로 사용합니다.
ThingsDB에서 쿼리를 실행하거나 프로시저를 실행할 때 실행할 컬렉션을 지정해야 합니다. 때로는 제한적일 수 있으며 다른 컬렉션에서 쿼리를 실행하거나 프로시저를 실행해야 하는 경우 이를 달성하는 방법이 있습니다. 특정 사용자로서 컬렉션의 다른 컬렉션에 액세스할 수 있게 해주는 thingsdb(book, GitHub)라는 모듈이 있습니다. 내 데모에서는 사용자 계정을 처리할 때 이 기능을 많이 사용하므로 여기서 언급합니다. 매뉴얼에 설명된 대로 이 모듈을 설치했습니다.
권한에 대해서는 나중에 설명하겠지만 참고로 이 모듈을 위해 만든 사용자 계정에는 @thingsdb 컬렉션에 대한 쿼리, 변경, 부여 권한과 @:stuff 컬렉션에 대한 변경, 부여 권한이 있습니다.
ThingsDB만 사용하기로 결정했기 때문에 해당 사용자 계정을 사용해야 했습니다. 백엔드가 없기 때문에 등록과 로그인을 처리해야 했는데 조금 까다로웠습니다. 물론 제3자 인증 서버(auth0 등)를 사용할 수도 있지만 다른 것에 의존하고 싶지는 않았습니다.
타사 인증 시스템을 구현하려는 경우 요청 모듈(도서, GitHub)을 사용하여 ThingsDB에서 HTTP 요청을 수행할 수 있습니다.
사용자가 등록할 수 있도록 하려면 ThingsDB와 통신하고 등록을 실행하기 위한 사용자 계정이 필요했습니다. 하지만 이 계정에 필요한 자격 증명은 매우 안전하지 않은 자바스크립트 코드로 게시됩니다. 모든 보안 문제를 다루고 싶지는 않았지만 최소한 간단한 보안 문제라도 구현하고 싶었습니다. ThingsDB는 각 컬렉션에 대해 각 사용자 계정에 대한 권한 부여를 구체적으로 지원합니다. 부여할 수 있는 권한은 쿼리, 변경, 부여, 가입 및 실행입니다.
쿼리를 전혀 사용할 수 없습니다. 이 명령을 사용하면 ThingsDB에서 무엇이든 실행할 수 있고 이를 클라이언트 브라우저에 열면 큰 보안 문제가 발생하기 때문입니다. 경로는 명확했고 프로시저를 사용해야 했고 클라이언트에 대해 실행만 허용해야 했습니다.
알아두어야 할 중요한 정보는 사용자 계정에 비밀번호뿐만 아니라 액세스 토큰(필요한 경우 만료 있음)도 있다는 것입니다.
컬렉션 @:auth와 이름이 aa(auth 계정)인 사용자 계정을 생성했으며 그에게 이 컬렉션에 대한 실행 권한을 부여했습니다. 컬렉션 @:auth에는 Register라는 프로시저가 하나만 포함되어 있습니다. 이 모든 것은 사용자 aa가 등록이라는 프로시저를 실행하는 한 가지 작업만 수행할 수 있음을 의미합니다. 따라서 그의 액세스 토큰이 게시될 수 있습니다.
절차 등록에서는 새 계정을 생성하고 필요한 권한을 부여합니다. 코드는 다음과 같습니다.
new_procedure('register', |email, password| { if (email.len() == 0 || password.len() == 0 || !is_email(email)) { raise('required values not provided'); }; thingsdb.query('@t', " if (has_user(email)) { raise('email already registered'); }; new_user(email); set_password(email, password); grant('@:stuff', email, RUN | CHANGE); ", { email:, password:, }); nil; });
ThingsDB의 코드를 처음 보는 것 같아요. 약간만 변경하면 다른 프로그래밍 언어에도 친숙해집니다. 절차의 역할:
email:, 약간 혼란스러울 수 있지만 변수를 인수에 전달하려고 하며 변수의 이름이 같을 때 간략하게 설명합니다.
@t는 @thingsdb 범위의 단축어입니다.
ThingsDB 측의 모든 준비가 완료되면 등록 양식과 몇 줄의 자바스크립트로 간단한 웹사이트를 만들었습니다. ThingsDB 내부에서 프로시저를 실행하는 코드 조각은 다음과 같습니다.
const thingsdb = new ThingsDB(); thingsdb.connect() .then(() => thingsdb.authToken(localStorage.getItem('aa'))) .then(() => thingsdb.run('@:auth', 'register', [ $('#email').val(), $('#password1').val() ]))
브라우저 localStorage에 사용자 aa의 액세스 토큰을 보관합니다.
전체 구현을 보려면 여기를 확인하세요.
사용자가 등록할 수 있게 된 후 다음 단계는 로그인 작업을 구현하는 것이었습니다. 로그인하려면 비밀번호가 필요하지만 브라우저에 사용자 비밀번호를 저장하는 것은 그리 안전하지 않습니다. 해결책은 로그인 후 액세스 토큰(만료 포함)을 생성하고 이를 클라이언트에 반환하여 브라우저(예: sessionStorage)에 저장할 수 있는 것입니다. 그래서 등록된 사용자 계정에 필요한 권한이 있는 @:stuff 컬렉션에 프로시저를 만들었습니다.
new_procedure('login', || { email = user_info().load().name; if (is_email(email)) { thingsdb.query('@t', "new_token(email, datetime().move('days', 1));", {email: }) .then(|token| token); }; });
토큰 생성은 @thingsdb 범위에서 호출되어야 하며, 이 경우 thingsdb 모듈을 다시 사용합니다. 이 프로시저를 호출하는 자바스크립트 코드 조각은 다음과 같습니다.
const thingsdb = new ThingsDB(); thingsdb.connect() .then(() => thingsdb.auth($('#email').val(), $('#password').val())) .then(() => thingsdb.run('@:stuff', 'login')) .then(token => { sessionStorage.setItem('token', token); window.location.href = './overview.html'; })
얻은 액세스 토큰은 sessionStorage에 저장됩니다.
여기에서 로그인 양식과 필수 자바스크립트 코드가 포함된 전체 로그인 페이지를 확인할 수 있습니다.
로그인 후 사용자는 일부 계정 작업과 할 일 목록이 있는 여기로 리디렉션됩니다. 이는 구조를 지정하고 Todo 데이터가 저장되는 방법을 지정하는 데 필요하며 이 목적을 위해 데이터 유형을 사용할 수 있습니다. 이름, user_id, 항목이 포함된 Todo 유형을 만들었습니다. 항목 유형에는 설명, 확인된 상태 및 Todo 참조가 있습니다. Todo와 Item의 연결은 양방향 관계(책, 문서)로 이루어집니다. 두 유형 모두 @:stuff 컬렉션에 정의되어 있습니다.
new_type('Item'); new_type('Todo'); set_type('Item', { description: "'str'," checked: 'bool', todo: 'Todo?', }); set_type('Todo', { name: 'str', items: '{Item}', user_id: 'int', }); mod_type('Item', 'rel', 'todo', 'items');
이 코드에서는 유형이 어떻게 만들어지는지, 데이터 유형이 있는 속성은 무엇인지, 유형 간의 관계가 설정되는지 확인할 수 있습니다.
그러나 이것은 단지 정의일 뿐입니다. Todo를 어딘가에 저장해야 합니다. 이를 위해 우리는 이와 같이 @:stuff 컬렉션에 직접 속성을 만듭니다. 점이 없으면 가변적일 뿐 지속성이 없습니다.
.todos = set();
이제 데이터 구조가 준비되었으면 각 작업을 살펴보겠습니다.
개요 페이지 로딩 시 사용자의 Todo를 ThingsDB에 로딩하도록 요청합니다. 먼저 Todos 목록을 반환하는 @:stuff 컬렉션에 대한 절차가 필요합니다.
new_procedure('list_todos', || { user_id = user_info().load().user_id; .todos.filter(|t| t.user_id == user_id); });
필터는 촬영장에서 호출할 수 있는 기능입니다.
이제 다음과 같이 자바스크립트 코드 조각을 사용하여 이 프로시저를 호출할 수 있습니다(수신된 데이터 처리는 생략됨).
const thingsdb = new ThingsDB(); thingsdb.connect() .then(() => thingsdb.authToken(sessionStorage.getItem('token'))) .then(() => thingsdb.run('@:stuff', 'list_todos')) .then(todos => { })
여기에서 전체 구현을 확인할 수 있습니다.
이 작업을 위해 thingsdb 모듈을 다시 사용해야 하는 update_password 프로시저를 만들었습니다. 사용자 계정은 @thingsdb 범위에 저장됩니다.
new_procedure('update_password', |password| { email = user_info().load().name; if (is_email(email)) { thingsdb.query('@t', 'set_password(email, password);', { email:, password:, }); }; });
저는 html 대화 상자 태그를 사용하여 새 비밀번호를 입력하고 이를 처리하는 자바스크립트 코드 조각은 매우 간단합니다.
thingsdb.run('@:stuff', 'update_password', [$('#password1').val()])
Todos 로드 요청에서 웹소켓 연결이 아직 열려 있기 때문에 authToken을 다시 호출할 필요가 없습니다.
여기에서 전체 구현을 확인할 수 있습니다.
이 작업을 수행하면 사용자 계정뿐만 아니라 해당 사용자의 할 일도 제거됩니다. 다음과 같습니다:
new_procedure('delete_user', || { email = user_info().load().name; if (is_email(email)) { .todos.remove(|todo| todo.user_id == user_id); thingsdb.query('@t', 'del_user(email);', {email: }); }; });
Remove is another function which can be called on set.
I had to use thingsdb module again. User accounts are stored in @thingsdb scope.
Call of this procedure can be done easily with javascript code snippet:
thingsdb.run('@:stuff', 'delete_user')
I don't have to call authToken again because websocket connection is still open from the request to load Todos.
Look at the whole implementation here:
User need a way to create new Todo. For that reason I made page new_todo and overview contains link to it. Form to create todo consist of todo name and items (descriptions). I decided to store new Todo with items in two steps, because originally I wanted to allow editing of Todo (which in the end didn't happen). Therefore I've created two new procedures.
new_procedure('create_todo', |name| { t = Todo{ name:, user_id: user_info().load().user_id, }; .todos.add(t); t.id(); }); new_procedure('add_todo_items', |todo_id, items| { todo = thing(todo_id); if (todo.user_id != user_info().load().user_id) { raise('Not yours'); }; todo.items.clear(); items.each(|i| { item = Item{ checked: false, description: "i," }; todo.items.add(item); }); });
First procedure to create todo returns it's id and second procedure deletes all items and adds new ones. I think if you read until here you are already getting hang of it and I don't have to explain .todos.add() or items.each() (set add, thing each).
What is new here is thing(todo_id). You can get reference to any thing (thing is like instance of class/data type) from collection by id. You don't have to know where is stored, you can just get it. Thing has assigned id when is stored persistently.
To perform defined action you just have to call it with javascript code snippet:
thingsdb.run('@:stuff', 'create_todo', [$('#name').val()]) .then((todo) => thingsdb.run('@:stuff', 'add_todo_items', [ todo, items.length ? items.map(function () { return $(this).val(); }).get() : [] ]))
Look at the whole implementation here:
Overview page shows list of user Todos. By clicking on it user is redirected to page where he can see Todo items, change their status and delete whole Todo list.
To load one specific Todo I've created new procedure:
new_procedure('list_todo', |todo_id| { todo = thing(todo_id); if (todo.user_id != user_info().load().user_id) { raise('Not yours'); }; return todo, 2; });
Now you are propably asking why there is return todo, 2;? With return you can set depth of data you want to return. With number 2 here returned data contains not only Todo itself, but also Items the Todo has relation with.
Because Todo id is passed as uri get parameter, the javascript code snippet to call this procedure looks like this:
thingsdb.run('@:stuff', 'list_todo', [ parseInt(location.search.match(/id=(\d+)/)[1]) ])
Look at the whole implementation here:
todo.html
todo.js
I render todo items as checklist, so to change status of item I've created new procedure:
new_procedure('mark_item', |item_id, checked| { item = thing(item_id); if (item.todo.user_id != user_info().load().user_id) { raise('Not yours'); }; item.checked = checked; nil; });
Because you can also uncheck, not only check item, javascript code snippet has to be like this:
thingsdb.run('@:stuff', 'mark_item', [ parseInt(this.id), $(this).is(':checked') ])
Look at the whole implementation here:
todo.html
todo.js
If we want to delete Todo, we don't have to delete items because they are not stored separately. If Todo is removed, no other reference exists for its items and they are automatically removed.
new_procedure('delete_todo', |todo_id| { todo = thing(todo_id); if (todo.user_id != user_info().load().user_id) { raise('Not yours'); }; .todos.remove(todo); });
Now the javascript code snippet is simple:
thingsdb.run('@:stuff', 'delete_todo', [ parseInt(location.search.match(/id=(\d+)/)[1]) ])
Look at the whole implementation here:
todo.html
todo.js
To simplify usage of this demo you can run ThingsDB in docker with Dockerfile. At the end of this file you find required commands as comments. Instance of ThingsDB made with this Dockerfile is based on specific branch which was not yet released and introduces using user_info() inside of collections.
Next simply open install.html which creates everything required in this ThingsDB instance and store access token of aa user to localStorage.
That's it. I hope I gave you basic insight into this technology. If you like my work you can buy me a tea.
No AI was used to generate this content, only the cover picture.
위 내용은 ThingsDB Todo 앱 데모를 설명하겠습니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!