init commit,
This commit is contained in:
47
02_design/index.md
Normal file
47
02_design/index.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# design
|
||||
|
||||
## POC:
|
||||
|
||||
### poc_supabase_budibase
|
||||
|
||||
- supabase
|
||||
- budibase (admin frontend)
|
||||
|
||||
### poc_supabase_refine
|
||||
|
||||
- supabase
|
||||
- refine
|
||||
|
||||
### poc_supabase_seeding
|
||||
|
||||
- [Seeding your database](https://supabase.com/docs/guides/cli/seeding-your-database)
|
||||
|
||||
### poc_connectivity
|
||||
|
||||
- supabase(real time db)
|
||||
- ionic realtime chat
|
||||
|
||||
### poc_connectivity
|
||||
|
||||
- supabase
|
||||
- ionic (perform CRUD)
|
||||
|
||||
### poc_supabase integration (Official Starters)
|
||||
|
||||
- https://github.com/lyqht/awesome-supabase
|
||||
- Next.js, Slack Clone
|
||||
- React
|
||||
|
||||
### poc_supabase_migration_example
|
||||
|
||||
- https://supabase.com/docs/guides/getting-started/tutorials/with-refine
|
||||
|
||||
- [design](#design)
|
||||
- [POC](#poc)
|
||||
- [poc_connectivity](#poc_connectivity)
|
||||
- [poc_supabase_seeding](#poc_supabase_seeding)
|
||||
- [poc_connectivity](#poc_connectivity-1)
|
||||
- [poc_connectivity](#poc_connectivity-2)
|
||||
- [poc_connectivity](#poc_connectivity-3)
|
||||
- [poc_supabase integration (Official Starters):](#poc_supabase-integration-official-starters)
|
||||
- [poc_supabase_migration_example](#poc_supabase_migration_example)
|
7
02_design/reset_permissions.sh
Executable file
7
02_design/reset_permissions.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
sudo chown 1000:1000 -R .
|
||||
|
||||
echo "done"
|
31
02_design/schema/flatten.js
Normal file
31
02_design/schema/flatten.js
Normal file
@@ -0,0 +1,31 @@
|
||||
const fs = require('fs');
|
||||
|
||||
|
||||
const schemaSql = fs.readFileSync('schema.sql', 'utf8');
|
||||
|
||||
var schemaSqlTrimmed = schemaSql.replace(/^\n$/g, '');
|
||||
schemaSqlTrimmed = schemaSqlTrimmed.replace(/\(\n/g, '(');
|
||||
schemaSqlTrimmed = schemaSqlTrimmed.replace(/\n /g, ' ');
|
||||
schemaSqlTrimmed = schemaSqlTrimmed.replace(/\n\)/g, ')');
|
||||
schemaSqlTrimmed = schemaSqlTrimmed.replace(/,\n/g, '');
|
||||
|
||||
for (let i =0 ; i<10; i++){
|
||||
schemaSqlTrimmed = schemaSqlTrimmed.replace(/\n\n/g, '\n');
|
||||
}
|
||||
|
||||
// split schemaSqlTrimmed on 'by \n'
|
||||
const schemaSqlSplit = schemaSqlTrimmed.split('\n');
|
||||
const commentIndex = schemaSqlSplit.findIndex(line => line.match(/^COMMENT/));
|
||||
const alterIndex = schemaSqlSplit.findIndex(line => line.match(/^ALTER/));
|
||||
console.log({commentIndex, alterIndex});
|
||||
|
||||
const createSql = schemaSqlSplit.slice(0, commentIndex).join('\n');
|
||||
fs.writeFileSync('create.sql', createSql);
|
||||
|
||||
const commentSql = schemaSqlSplit.slice(commentIndex, alterIndex).join('\n');
|
||||
fs.writeFileSync('comment.sql', commentSql);
|
||||
|
||||
const alterSql = schemaSqlSplit.slice( alterIndex).join('\n');
|
||||
fs.writeFileSync('alter.sql', '-- \n'+'-- alter.sql\n'+ '-- \n' + alterSql);
|
||||
|
||||
// console.log({schemaSqlTrimmed})
|
22
02_design/schema/modify_fake_table.js
Normal file
22
02_design/schema/modify_fake_table.js
Normal file
@@ -0,0 +1,22 @@
|
||||
// process fake_auth.user to auth.user
|
||||
|
||||
// 1. open `schema.sql`
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
var schema = fs.readFileSync(path.join(__dirname, 'schema.sql'), 'utf8');
|
||||
|
||||
var schema_before = schema
|
||||
// 2. replace `CREATE TABLE "fake_auth.users" (*)` to ''
|
||||
schema = schema.replace(/CREATE TABLE "fake_auth"\."users" \(.*?\);/gs, '-- fake_auth.users part syntax removed.');
|
||||
if (schema.length == schema_before.length)
|
||||
console.log("cannot replace !!!")
|
||||
|
||||
schema_before = schema
|
||||
// 3. modify `REFERENCES "fake_auth.users"` to `REFERENCES "auth.users"`
|
||||
schema = schema.replace(/REFERENCES "fake_auth"\."users"/g, 'REFERENCES auth.users');
|
||||
if (schema.length == schema_before.length)
|
||||
console.log("cannot replace !!!")
|
||||
|
||||
fs.writeFileSync(path.join(__dirname, 'schema.sql'), schema);
|
||||
|
||||
console.log("replace fake_auth.users to auth.users done . ")
|
1361
02_design/schema/output.svg
Normal file
1361
02_design/schema/output.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 166 KiB |
372
02_design/schema/schema.dbml
Normal file
372
02_design/schema/schema.dbml
Normal file
@@ -0,0 +1,372 @@
|
||||
// HKSingleParty
|
||||
|
||||
Table fake_auth.users {
|
||||
id uuid [pk]
|
||||
username varchar
|
||||
remarks text
|
||||
}
|
||||
|
||||
// table above this line is from supabase, for graphing / generating sql
|
||||
|
||||
// filename: 002_message_status.sql
|
||||
Table message_status {
|
||||
id serial [pk]
|
||||
title text [not null, default: ''] // read, unread
|
||||
remarks text [not null, default: '']
|
||||
//
|
||||
create_at TIMESTAMPTZ [not null, default: `CURRENT_TIMESTAMP`]
|
||||
update_at TIMESTAMPTZ [not null, default: `now()`]
|
||||
meta json [not null, default: `'{}'::json`]
|
||||
//
|
||||
note: 'message read/unread status'
|
||||
}
|
||||
|
||||
// REQ0002/vip-tag
|
||||
// store user service rank,
|
||||
// `not_vip`, not vip
|
||||
// `vip`, vip user
|
||||
Table user_rank {
|
||||
id serial [pk]
|
||||
title text [not null, default: '']
|
||||
remarks text [not null, default: '']
|
||||
//
|
||||
create_at TIMESTAMPTZ [not null, default: `CURRENT_TIMESTAMP`]
|
||||
update_at TIMESTAMPTZ [not null, default: `now()`]
|
||||
meta json [not null, default: `'{}'::json`]
|
||||
//
|
||||
note: 'store user vip status field'
|
||||
}
|
||||
|
||||
// REQ0112/verified-tag
|
||||
// store user verified status,
|
||||
// `not_verified`, not verified
|
||||
// `verified`, verified
|
||||
Table user_verified {
|
||||
id serial [pk]
|
||||
title text [not null, default: '']
|
||||
remarks text [not null, default: '']
|
||||
//
|
||||
create_at TIMESTAMPTZ [not null, default: `CURRENT_TIMESTAMP`]
|
||||
update_at TIMESTAMPTZ [not null, default: `now()`]
|
||||
meta json [not null, default: `'{}'::json`]
|
||||
//
|
||||
note: 'store user verified field'
|
||||
}
|
||||
|
||||
// REQ0092/user-other-tags
|
||||
Table user_other_tags {
|
||||
id serial [pk]
|
||||
title text [not null, default: '']
|
||||
remarks text [not null, default: '']
|
||||
//
|
||||
create_at TIMESTAMPTZ [not null, default: `CURRENT_TIMESTAMP`]
|
||||
update_at TIMESTAMPTZ [not null, default: `now()`]
|
||||
meta json [not null, default: `'{}'::json`]
|
||||
//
|
||||
note: 'store user tags field'
|
||||
}
|
||||
|
||||
// REQ0043/profile_detail
|
||||
Table user_spoken_language {
|
||||
id serial [pk]
|
||||
title text [not null, default: '']
|
||||
remarks text [not null, default: '']
|
||||
//
|
||||
create_at TIMESTAMPTZ [not null, default: `CURRENT_TIMESTAMP`]
|
||||
update_at TIMESTAMPTZ [not null, default: `now()`]
|
||||
meta json [not null, default: `'{}'::json`]
|
||||
//
|
||||
note: 'store user spoken language field'
|
||||
}
|
||||
|
||||
// REQ0094/user-career-filter
|
||||
Table user_career {
|
||||
id serial [pk]
|
||||
title text [not null, default: '']
|
||||
remarks text [not null, default: '']
|
||||
//
|
||||
create_at TIMESTAMPTZ [not null, default: `CURRENT_TIMESTAMP`]
|
||||
update_at TIMESTAMPTZ [not null, default: `now()`]
|
||||
meta json [not null, default: `'{}'::json`]
|
||||
//
|
||||
note: 'store user career field'
|
||||
}
|
||||
|
||||
// REQ0095/user-education-filter
|
||||
Table user_education {
|
||||
id serial [pk]
|
||||
title text [not null, default: '']
|
||||
remarks text [not null, default: '']
|
||||
//
|
||||
create_at TIMESTAMPTZ [not null, default: `CURRENT_TIMESTAMP`]
|
||||
update_at TIMESTAMPTZ [not null, default: `now()`]
|
||||
meta json [not null, default: `'{}'::json`]
|
||||
//
|
||||
note: 'store user education field'
|
||||
}
|
||||
|
||||
// REQ0051/notification
|
||||
// to extend -> Database design for notification system
|
||||
// https://tannguyenit95.medium.com/designing-a-notification-system-1da83ca971bc
|
||||
Table notifications {
|
||||
id serial [pk]
|
||||
content text [not null, default: '']
|
||||
remarks text [not null, default: '']
|
||||
//
|
||||
create_at TIMESTAMPTZ [not null, default: `CURRENT_TIMESTAMP`]
|
||||
update_at TIMESTAMPTZ [not null, default: `now()`]
|
||||
meta json [not null, default: `'{}'::json`]
|
||||
//
|
||||
note: 'handle admin send notification to user'
|
||||
}
|
||||
|
||||
// REQ0047/order-page
|
||||
// REQ0042/event-detail
|
||||
// store party event cancelled, ongoing
|
||||
Table party_event_status {
|
||||
id serial [pk]
|
||||
title text [not null, default: '']
|
||||
//
|
||||
create_at TIMESTAMPTZ [not null, default: `CURRENT_TIMESTAMP`]
|
||||
update_at TIMESTAMPTZ [not null, default: `now()`]
|
||||
meta json [not null, default: `'{}'::json`]
|
||||
//
|
||||
note: 'store party event status'
|
||||
}
|
||||
|
||||
// REQ0041/home_discover_event_tab
|
||||
Table party_events {
|
||||
id serial [pk]
|
||||
title text [not null, default: '']
|
||||
about_event text [not null, default: ''] // store about the event field
|
||||
//
|
||||
event_date TIMESTAMPTZ [not null, default: `now()`]
|
||||
event_duration_min integer [not null, default: 90]
|
||||
//
|
||||
price integer [not null, default: 999]
|
||||
currency text [not null, default: 'HKD']
|
||||
last_payment_time TIMESTAMPTZ [not null, default: '2999-01-01']
|
||||
//
|
||||
address text [not null, default: '']
|
||||
image_urls text[] [default: '{}']
|
||||
//
|
||||
status integer [not null, ref: > party_event_status.id, default: 0]
|
||||
//
|
||||
// replaced by party_event_orders table
|
||||
// participants uuid [not null, ref: <> fake_auth.users.id]
|
||||
join_boy integer [not null, default: 999]
|
||||
join_girl integer [not null, default: 999]
|
||||
//
|
||||
create_at TIMESTAMPTZ [not null, default: `CURRENT_TIMESTAMP`]
|
||||
update_at TIMESTAMPTZ [not null, default: `now()`]
|
||||
remarks text [not null, default: '']
|
||||
meta json [not null, default: `'{}'::json`]
|
||||
//
|
||||
note: 'store party events'
|
||||
}
|
||||
|
||||
Table user_genders {
|
||||
id serial [pk]
|
||||
title text [not null, default: '']
|
||||
//
|
||||
create_at TIMESTAMPTZ [not null, default: `CURRENT_TIMESTAMP`] // order time ?
|
||||
update_at TIMESTAMPTZ [not null, default: `now()`]
|
||||
meta json [not null, default: `'{}'::json`]
|
||||
//
|
||||
note: 'store user party event orders status'
|
||||
}
|
||||
|
||||
// REQ0053/Profile-page
|
||||
Table profiles {
|
||||
// provided by supabase auth
|
||||
id uuid [pk, ref: - fake_auth.users.id]
|
||||
//
|
||||
user_id serial [pk, unique]
|
||||
website text [not null, default: '']
|
||||
gender integer [not null, ref: - user_genders.id, default: 1]
|
||||
|
||||
// store user bookmarked event
|
||||
bookmark_events integer [unique, ref: <> party_events.id]
|
||||
|
||||
// REQ0044/near_by_page
|
||||
// TODO: fix coordinates later
|
||||
// coordinates point [default: 'POINT(114.1759 22.3271)']
|
||||
|
||||
// REQ0099/profile-edit-form start
|
||||
avatar_urls text[] [default: '{}']
|
||||
about_user text [not null, default: '']
|
||||
|
||||
spoken_language integer [unique, ref: <> user_spoken_language.id]
|
||||
career integer [unique, ref: <> user_career.id]
|
||||
user_rank integer [unique, ref: <> user_rank.id] // store user vip rank
|
||||
verified integer [unique, ref: - user_verified.id]
|
||||
weight_kg integer [not null, default: 100]
|
||||
height_cm integer [not null, default: 100]
|
||||
education integer [unique, ref: - user_education.id]
|
||||
|
||||
other_tags integer [unique, ref: <> user_other_tags.id]
|
||||
// REQ0099/profile-edit-form end
|
||||
|
||||
// store last onlnie information for My-nearby
|
||||
last_seen TIMESTAMPTZ [not null, default: `now()`]
|
||||
|
||||
// get age
|
||||
year_of_birth integer [default: 1970] // default to Jan-01 to get age
|
||||
|
||||
// REQ0051/notification
|
||||
notification_read integer [unique, ref: <> notifications.id]
|
||||
// REQ0051/notification
|
||||
|
||||
|
||||
block_user_id text[] [not null, default: '{}']
|
||||
|
||||
// handle user delete account
|
||||
// is_user_suspend -> user delete account
|
||||
// is_admin_suspend -> admin delete user account
|
||||
suspend_at TIMESTAMPTZ [default: '2999-01-01']
|
||||
is_user_suspend integer [not null, default: 0] // 0 => not suspended
|
||||
is_admin_suspend integer [not null, default: 0] // 0 => not suspended
|
||||
|
||||
//
|
||||
create_at TIMESTAMPTZ [not null, default: `CURRENT_TIMESTAMP`]
|
||||
update_at TIMESTAMPTZ [not null, default: `now()`]
|
||||
meta json [not null, default: `'{}'::json`]
|
||||
//
|
||||
note: 'store user meta'
|
||||
}
|
||||
|
||||
// REQ0050/chat-room-list (messages)
|
||||
Table messages {
|
||||
id serial [pk]
|
||||
content text [not null, default: '']
|
||||
media_url varchar(255)
|
||||
|
||||
sender_user_id integer [not null, ref: > profiles.user_id]
|
||||
receiver_user_id integer [not null, ref: > profiles.user_id]
|
||||
|
||||
// NOTE: ?? not sure correct or not for these
|
||||
// message_arrived integer [not null, unique, ref: <> profiles.user_id]
|
||||
// message_read integer [not null, unique, ref: <> profiles.user_id]
|
||||
// message_archived integer [not null, unique, ref: <> profiles.user_id]
|
||||
// message_deleted integer [not null, unique, ref: <> profiles.user_id]
|
||||
//
|
||||
create_at TIMESTAMPTZ [not null, default: `CURRENT_TIMESTAMP`]
|
||||
update_at TIMESTAMPTZ [not null, default: `now()`]
|
||||
meta json [not null, default: `'{}'::json`]
|
||||
//
|
||||
note: 'store in-app chat'
|
||||
}
|
||||
|
||||
Table visit_user_profile_read_status {
|
||||
id serial [pk]
|
||||
title text [not null, default: ''] // unread -> read
|
||||
remarks text [not null, default: '']
|
||||
//
|
||||
create_at TIMESTAMPTZ [not null, default: `CURRENT_TIMESTAMP`]
|
||||
update_at TIMESTAMPTZ [not null, default: `now()`]
|
||||
meta json [not null, default: `'{}'::json`]
|
||||
//
|
||||
note: 'store visit status read by host'
|
||||
}
|
||||
|
||||
// REQ0050/chat-room-list
|
||||
// visitor => some people visit host profile
|
||||
// host => profile that visitor visit
|
||||
// on host perspective, store who visit host profile
|
||||
Table visit_user_profile {
|
||||
id serial [pk]
|
||||
//
|
||||
visitor_user_id integer [not null, ref: > profiles.user_id]
|
||||
visit_at TIMESTAMPTZ [not null, default: `now()`]
|
||||
|
||||
// question, why i think `<` is correct ?
|
||||
host_user_id integer [not null, ref: > profiles.user_id]
|
||||
host_user_read integer [not null, ref: > visit_user_profile_read_status.id, default: 1]
|
||||
|
||||
//
|
||||
create_at TIMESTAMPTZ [not null, default: `CURRENT_TIMESTAMP`]
|
||||
update_at TIMESTAMPTZ [not null, default: `now()`]
|
||||
meta json [not null, default: `'{}'::json`]
|
||||
remarks text [not null, default: '']
|
||||
//
|
||||
note: 'history list, store user profile visited by others, used in near-by page'
|
||||
}
|
||||
|
||||
// not-paid, user not paid for event
|
||||
// paid, user paid for event
|
||||
Table party_event_order_status {
|
||||
id serial [pk]
|
||||
title text [not null, default: ''] // pending, paid
|
||||
remarks text [not null, default: '']
|
||||
//
|
||||
create_at TIMESTAMPTZ [not null, default: `CURRENT_TIMESTAMP`] // order time ?
|
||||
update_at TIMESTAMPTZ [not null, default: `now()`]
|
||||
meta json [not null, default: `'{}'::json`]
|
||||
//
|
||||
note: 'store user party event orders status'
|
||||
}
|
||||
|
||||
// REQ0047/order-page
|
||||
Table party_event_orders {
|
||||
id serial [pk]
|
||||
user_id integer [not null, ref: - profiles.user_id]
|
||||
party_event_id integer [not null, ref: - party_events.id]
|
||||
//
|
||||
status integer [not null, ref: - party_event_order_status.id]
|
||||
payment_time TIMESTAMPTZ [not null, default: '2999-01-01']
|
||||
//
|
||||
create_at TIMESTAMPTZ [not null, default: `CURRENT_TIMESTAMP`] // order time
|
||||
update_at TIMESTAMPTZ [not null, default: `now()`]
|
||||
remarks text [default: '']
|
||||
meta json [not null, default: `'{}'::json`]
|
||||
//
|
||||
note: 'store user party event orders'
|
||||
}
|
||||
|
||||
// REQ0057/contact-support
|
||||
Table contact_us_message {
|
||||
id serial [pk]
|
||||
content text [not null, default: '']
|
||||
attachments text[] [default: '{}']
|
||||
read_status integer [not null, ref: - message_status.id, default: 1]
|
||||
//
|
||||
from_user_id integer [not null, ref: - profiles.user_id]
|
||||
//
|
||||
create_at TIMESTAMPTZ [not null, default: `CURRENT_TIMESTAMP`]
|
||||
update_at TIMESTAMPTZ [not null, default: `now()`]
|
||||
remarks text [not null, default: '']
|
||||
meta json [not null, default: `'{}'::json`]
|
||||
//
|
||||
note: 'store user to admin messages'
|
||||
}
|
||||
|
||||
// REQ0084/select-report-category
|
||||
Table report_user_reason {
|
||||
id serial [pk]
|
||||
content text [not null, default: '']
|
||||
//
|
||||
create_at TIMESTAMPTZ [not null, default: `CURRENT_TIMESTAMP`]
|
||||
update_at TIMESTAMPTZ [not null, default: `now()`]
|
||||
remarks text [not null, default: '']
|
||||
meta json [not null, default: `'{}'::json`]
|
||||
//
|
||||
note: 'store user to admin messages'
|
||||
}
|
||||
|
||||
// REQ0046/report-form
|
||||
Table report_user {
|
||||
id serial [pk]
|
||||
content text [not null, default: '']
|
||||
attachments text[] [default: '{}']
|
||||
reason integer [not null, ref: - report_user_reason.id]
|
||||
//
|
||||
report_user_id integer [not null, ref: - profiles.user_id] // store user id to report
|
||||
from_user_id integer [not null, ref: - profiles.user_id] // store report sender
|
||||
//
|
||||
create_at TIMESTAMPTZ [not null, default: `CURRENT_TIMESTAMP`]
|
||||
update_at TIMESTAMPTZ [not null, default: `now()`]
|
||||
remarks text [not null, default: '']
|
||||
meta json [not null, default: `'{}'::json`]
|
||||
//
|
||||
note: 'store report user messages'
|
||||
}
|
BIN
02_design/schema/snapshot.png
Normal file
BIN
02_design/schema/snapshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 119 KiB |
21
02_design/schema/update.sh
Executable file
21
02_design/schema/update.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
# Install pre-requisites
|
||||
# npm install -g @softwaretechnik/dbml-renderer
|
||||
|
||||
# TO RUN
|
||||
#
|
||||
# cd /home/logic/_wsl_workspace/HKSingleParty/02_design/schema
|
||||
# nodemon --ext dbml -w . --exec "./update.sh"
|
||||
#
|
||||
|
||||
npx -p @dbml/cli dbml2sql ./schema.dbml -o ./schema.sql
|
||||
npx dbml-renderer -i schema.dbml -o output.svg
|
||||
|
||||
# process fake_auth.user to auth.user
|
||||
node ./modify_fake_table.js
|
||||
|
||||
# move to target sql file
|
||||
mv ./schema.sql ../../04_poc/009_poc_chatroom_implement/docker/supabase/migrations/00003_party_schema_dbml.sql
|
26
02_design/tables.md
Normal file
26
02_design/tables.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# tables.md
|
||||
|
||||
### table check list
|
||||
|
||||
| `table` | `insert sample data` |
|
||||
| -------------------------- | :------------------: |
|
||||
| `message_status` | ✅ |
|
||||
| `user_rank` | ✅ |
|
||||
| `user_verified` | ✅ |
|
||||
| `user_other_tags` | ✅ |
|
||||
| `user_spoken_language` | ✅ |
|
||||
| `user_career` | ✅ |
|
||||
| `user_education` | ✅ |
|
||||
| `notifications` | ✅ |
|
||||
| `party_event_status` | ✅ |
|
||||
| `party_events` | ✅ |
|
||||
| `user_genders` | ✅ |
|
||||
| `visit_user_status` | ✅ |
|
||||
| `contact_us_message` | ✅ |
|
||||
| `report_user_reason` | ✅ |
|
||||
| `report_user` | ✅ |
|
||||
| `messages` | ✅ |
|
||||
| `profiles` | ✅ |
|
||||
| `visit_user_profile` | ✅ |
|
||||
| `party_event_order_status` | ✅ |
|
||||
| `party_event_orders` | |
|
17
02_design/time_track.md
Normal file
17
02_design/time_track.md
Normal file
@@ -0,0 +1,17 @@
|
||||
```mermaid
|
||||
|
||||
gantt
|
||||
title Project time track
|
||||
dateFormat YYYY-MM-DD
|
||||
|
||||
section Section
|
||||
poc :poc, 2024-08-30, 3d
|
||||
code android :android, after poc, 30d
|
||||
code admin panel :admin, after android, 30d
|
||||
code ios :after admin, 30d
|
||||
|
||||
section Another
|
||||
Task in Another :2024-08-30, 12d
|
||||
another task :24d
|
||||
|
||||
```
|
Reference in New Issue
Block a user