Beberapa masa lalu saya telah menemui pangkalan data yang dipanggil ThingsDB. Saya ingin tahu tentangnya dan saya membaca sedikit. Saya telah mendapati mereka menyokong sambungan TCP tetapi mereka tidak mempunyai pemacu untuk beberapa platform tertentu oleh itu saya telah membangunkan pemacu untuknya untuk javascript dan untuk php.
Apabila saya bekerja pada pemacu javascript, saya telah menyedari, adalah mungkin untuk menggunakan ThingsDB terus dari bahagian hadapan tanpa sebarang bahagian belakang atau perisian tengah. Anda boleh membuka sambungan websocket (TCP) daripada penyemak imbas, jadi saya menghubungi pengarang ThingsDB dan mereka menambah sokongan untuk websocket (tersedia daripada ThingsDB versi 1.6). Dengan cara ini pemacu javascript saya boleh digunakan dari frontend (pelayar) dan juga dari backend berasaskan javascript (cth. node.js). Saya menulis artikel di sini mengenai pemandu php saya di mana saya telah menerima maklum balas yang menarik. Orang ramai ingin melihat lebih banyak potensi ThingsDB. Berdasarkan itu saya memilih untuk tidak menulis artikel tentang pemacu javascript saya sejurus selepas saya selesai, tetapi saya telah memutuskan adalah lebih baik untuk membuat demo.
Untuk memahami asas ThingsDB dan demo ini, saya cadangkan anda membaca secara berterusan semasa saya menerangkan ciri-ciri tertentu dalam perjalanan. Saya menjangkakan anda sudah biasa dengan pengaturcaraan secara umum, sekurang-kurangnya asas. Dan mungkin beberapa javascript dan jQuery.
Jika anda ingin mengikuti artikel ini dengan melaksanakan coretan kod di dalam ThingsDB, anda perlu menggunakan fail docker yang dilampirkan yang disebut dalam panduan Pemasangan.
Perkara pertama dahulu. Biar saya terangkan secara ringkas strukturnya.
ThingsDB mengandungi koleksi. Pengumpulan mengandungi data, prosedur, tugas, jenis data dan enum. Terdapat juga koleksi terdahulu (skop) @thingsdb yang mengandungi akaun akses pengguna dan ia juga boleh mengandungi prosedur dan tugas. Akhir sekali terdapat skop @node yang tidak penting sekarang.
Semua perkara yang dinamakan seperti data, prosedur, tugas, jenis data dan enum ditakrifkan oleh pembangun yang melaksanakan ThingsDB. Contoh baharu pangkalan data ini hanya mengandungi koleksi kosong yang dipanggil @:stuff dan pentadbir akaun pengguna. Saya menggunakan koleksi ini sebagai koleksi utama untuk demo ini.
Apabila anda melaksanakan pertanyaan atau menjalankan prosedur pada ThingsDB, anda perlu menentukan koleksi mana yang akan dijalankan. Itu kadangkala boleh mengehadkan dan jika anda mempunyai keperluan untuk melaksanakan pertanyaan atau menjalankan prosedur pada koleksi lain, ada cara bagaimana untuk mencapainya. Terdapat modul yang dipanggil thingsdb (buku, GitHub) yang membolehkan anda mengakses koleksi lain daripada koleksi sebagai pengguna tertentu. Demo saya banyak menggunakan ciri ini apabila berurusan dengan akaun pengguna, itulah sebabnya saya menyebutnya di sini. Saya telah memasang modul ini seperti yang dijelaskan dalam manual.
Saya akan menerangkan kebenaran sedikit kemudian tetapi fyi: Akaun pengguna yang saya buat untuk modul ini mempunyai kebenaran Pertanyaan, Perubahan, Pemberian pada koleksi @thingsdb dan Perubahan, Pemberian pada koleksi @:bahan.
Saya memilih untuk menggunakan ThingsDB sahaja dan ini bermakna saya terpaksa menggunakan akaun pengguna mereka. Saya terpaksa berurusan dengan pendaftaran dan log masuk yang agak rumit kerana ketiadaan backend. Sudah tentu saya boleh menggunakan pelayan pengesahan pihak ketiga (auth0, dll.), tetapi saya tidak mahu bergantung pada apa-apa lagi.
Sekiranya seseorang ingin melaksanakan sistem pengesahan pihak ketiga, anda boleh melakukan permintaan HTTP daripada modul ThingsDB dengan Permintaan (buku, GitHub).
Untuk membolehkan pengguna mendaftar, saya memerlukan beberapa akaun pengguna untuk berkomunikasi dengan ThingsDB dan melaksanakan pendaftaran. Tetapi kelayakan yang diperlukan untuk akaun ini akan diterbitkan dalam kod javascript yang kedengarannya tidak begitu selamat. Saya tidak mahu menangani semua masalah keselamatan tetapi saya mahu melaksanakan sekurang-kurangnya yang mudah. ThingsDB menyokong pemberian kebenaran untuk setiap akaun pengguna terhadap setiap koleksi secara khusus. Kebenaran yang tersedia untuk diberikan ialah Pertanyaan, Tukar, Geran, Sertai dan Jalankan.
Saya tidak boleh menggunakan Pertanyaan sama sekali. Kerana dengan arahan ini anda boleh melaksanakan apa sahaja pada ThingsDB dan membukanya kepada pelayar klien menimbulkan masalah keselamatan yang besar. Laluan itu jelas, saya terpaksa menggunakan prosedur dan hanya membenarkan Jalankan untuk pelanggan.
Maklumat penting untuk diketahui ialah akaun pengguna tidak hanya mempunyai kata laluan tetapi juga token akses (dengan tamat tempoh jika perlu).
Saya telah mencipta koleksi @:auth dan akaun pengguna dengan nama aa (akaun auth) dan saya memberinya kebenaran Jalankan ke atas koleksi ini. Koleksi @:auth mengandungi hanya satu prosedur yang dipanggil daftar. Semua ini bermakna, pengguna aa hanya boleh melakukan satu perkara iaitu menjalankan prosedur yang dipanggil daftar. Oleh itu token aksesnya boleh diterbitkan.
Pendaftaran prosedur membuat akaun baharu dan memberikan kebenaran yang diperlukan. Kodnya kelihatan seperti ini:
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; });
Saya rasa ini kali pertama anda melihat kod daripada ThingsDB. Ia biasa kepada bahasa pengaturcaraan lain hanya dengan sedikit perubahan. Apa yang dilakukan oleh prosedur:
e-mel:, boleh menjadi sedikit mengelirukan tetapi ia adalah singkatan apabila anda ingin menghantar pembolehubah kepada hujah dan hujah dan pembolehubah mempunyai nama yang sama.
@t ialah pintasan untuk skop @thingsdb.
Dengan segala-galanya sedia di sebelah ThingsDB, saya telah mencipta laman web ringkas dengan borang pendaftaran dan beberapa baris javascript. Coretan kod yang berjaya menjalankan prosedur di dalam ThingsDB kelihatan seperti ini:
const thingsdb = new ThingsDB(); thingsdb.connect() .then(() => thingsdb.authToken(localStorage.getItem('aa'))) .then(() => thingsdb.run('@:auth', 'register', [ $('#email').val(), $('#password1').val() ]))
Saya menyimpan token akses pengguna aa dalam localStorage penyemak imbas.
Untuk melihat keseluruhan pelaksanaan lihat di sini:
Selepas pengguna dapat mendaftar, langkah seterusnya ialah melaksanakan tindakan log masuk. Untuk kata laluan log masuk diperlukan tetapi ia tidak begitu selamat untuk menyimpan kata laluan pengguna dalam pelayar. Penyelesaiannya adalah untuk menjana token akses (dengan tamat tempoh) selepas log masuk dan mengembalikannya kepada pelanggan, di mana ia boleh disimpan dalam penyemak imbas (cth. sessionStorage). Jadi saya telah mencipta prosedur dalam @: koleksi barangan yang mana akaun pengguna berdaftar memerlukan kebenaran.
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); }; });
Penciptaan token perlu dipanggil pada skop @thingsdb, sekiranya saya menggunakan modul thingsdb sekali lagi. Coretan kod javascript untuk memanggil prosedur ini kelihatan seperti ini:
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'; })
Token akses yang diperoleh disimpan dalam sessionStorage.
Di sini anda boleh menyemak keseluruhan halaman log masuk yang mengandungi borang log masuk dan kod javascript yang diperlukan:
Selepas pengguna log masuk diubah semula di sini di mana dia mempunyai beberapa tindakan akaun dan senarai Todosnya. Itu diperlukan untuk menentukan struktur, cara data Todo akan disimpan dan untuk tujuan ini kita boleh menggunakan jenis data. Saya mencipta jenis Todo yang mempunyai nama, id_pengguna dan item. Jenis Item mempunyai penerangan, status yang disemak dan rujukan Todo. Sambungan antara Todo dan Item dibuat dengan hubungan kedua-dua arah (buku, dokumen). Kedua-dua jenis ditakrifkan dalam @: koleksi barangan.
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');
Dalam sekeping kod ini, anda boleh melihat cara jenis dibuat, apakah sifat dengan jenis data yang mereka ada dan menetapkan hubungan antara mereka.
Tetapi ini hanyalah definisi. Kita perlu menyimpan Todos di suatu tempat. Untuk itu kami mencipta hartanah secara langsung pada koleksi @:barangan seperti ini. Tanpa titik ia hanya berubah-ubah dan ia tidak akan berterusan.
.todos = set();
Sekarang selepas struktur data sedia, mari kita lakukan setiap tindakan.
Setelah memuatkan halaman gambaran keseluruhan, permintaan untuk memuatkan Todos pengguna ke ThingsDB dibuat. Mula-mula kita memerlukan prosedur pada @: koleksi barangan yang mengembalikan senarai Todos:
new_procedure('list_todos', || { user_id = user_info().load().user_id; .todos.filter(|t| t.user_id == user_id); });
Fungsi penapis tersedia untuk dipanggil pada set.
Sekarang kita boleh memanggil prosedur ini dengan coretan kod javascript seperti ini (memproses data yang diterima ditinggalkan):
const thingsdb = new ThingsDB(); thingsdb.connect() .then(() => thingsdb.authToken(sessionStorage.getItem('token'))) .then(() => thingsdb.run('@:stuff', 'list_todos')) .then(todos => { })
Anda boleh menyemak keseluruhan pelaksanaan di sini:
Untuk tindakan ini saya telah mencipta prosedur kemas kini_kata laluan yang memerlukan untuk menggunakan modul thingsdb sekali lagi. Akaun pengguna disimpan dalam skop @thingsdb.
new_procedure('update_password', |password| { email = user_info().load().name; if (is_email(email)) { thingsdb.query('@t', 'set_password(email, password);', { email:, password:, }); }; });
Saya menggunakan teg dialog html untuk memasukkan kata laluan baharu dan coretan kod javascript untuk mengendalikannya adalah sangat mudah:
thingsdb.run('@:stuff', 'update_password', [$('#password1').val()])
Saya tidak perlu menghubungi authToken lagi kerana sambungan soket web masih dibuka daripada permintaan untuk memuatkan Todos.
Anda boleh menyemak keseluruhan pelaksanaan di sini:
Prosedur untuk tindakan ini mengalih keluar bukan sahaja akaun pengguna tetapi juga Todosnya. Ia kelihatan seperti ini:
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.
Atas ialah kandungan terperinci Biar saya terangkan demo aplikasi ThingsDB Todo. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!