#后端 #Web服务 #鸟巢 #观鸟者

app nestboxd

鸟巢清洁工的后端 - 不要笑 - 他们确实存在

1 个不稳定版本

0.1.0 2023年9月16日

#1558 in 数据库接口

AGPL-3.0-or-later

7.5MB
2K SLoC

nestbox

注意

我仍在开发中。我的目标是创建一个最小可行产品。这意味着

  • 拥有一个基于Web的GUI(缺失)
  • 拥有一个后端,能够
    • 根据扫描的QR码显示数据。
    • 验证用户身份
    • 接受经过身份验证和授权用户对鸟巢的修改

想法

一些观鸟者也在照顾鸟巢。因此,这些鸟巢在冬季通常会定期检查。为了给你一个想法,我们照顾着近400个盒子。如果一个鸟使用鸟巢,人们可以通过建造的巢来区分特定的鸟。然后观鸟者清理盒子,这意味着巢被移除,盒子准备下一次繁殖。通常还有一个列表,记录哪些鸟巢被哪些鸟使用以及哪一年。所以我的意图是给鸟巢清洁工一个工具,因为现在几乎每个人都有智能手机可用。所以关键的想法是

  • 每个鸟巢上都有一个QR码标签。
  • 经过认证的清洁工现在可以扫描QR码并执行以下操作。
    • 为新盒子设置新的地理位置,以便以后可以找到。
    • 设置检测到的繁殖(鸟的类型和年份)
  • 路过的人也可以扫描QR码并获得以下信息
    • 负责该盒子的协会以及如何联系他们。
    • 该盒子的历史,过去的繁殖情况。
  • 让照顾这些盒子的协会“走出黑暗”,因为他们在做有价值的工作,而许多人甚至不知道它们的存在。
  • 我个人觉得在业余时间开发这类应用程序很有趣。任何后端软件都使用Rust编写 - 并非因为我特别擅长Rust,而是因为我喜欢它,我也喜欢学习。

仓库

此仓库包含后端。

数据库模型

一般

尽管MongoDB是一个无模式的数据库,但模型的概念可以很容易地描述。注意,它只是一个骨架,这意味着我只包括我认为至关重要的属性。

uuid

在每条记录中都可以找到一个属性 uuid。它充当公共可见的主键,因为我认为不应该透露太多关于数据库的信息,而MongoDB对象ID是可猜测的。所以UUID是随机生成的。任何名为"uuid"的集合字段都是公共可访问的键,可以在URL等中使用。它们都是随机生成的类型4 UUID。

模型

在深入了解每个集合的细节之前,这里是一个快速概述各个集合之间的关系。


mandant 1 ----- n users
|
+----- 1 ------ n nestboxes 1 ----- n breeds
                  |
                  \ 1 ------------- n geolocation
                  

mandants

mandant是一个观鸟者的协会,如果你愿意的话。他们负责一个定义的地理区域内的一对鸟巢。一个记录现在包含以下属性

  • _id: ObjectId - 目前在任何上下文中都不使用。
  • uuid: 公共可访问密钥,例如在URL中。例如 e7620353-b6f6-47e9-b543-66af20769145
  • name: 关联名称,例如 BirdLife
  • website: 大多数这些关联都有自己的网站,例如 https://www.birdwatcher.ch
  • email: 显然,例如 [email protected]

users

用户属于一个客户,可以更改属于此客户的任何鸟箱。目前实现了一种基于简单密码的认证。属性

  • _id: ObjectId - 目前在任何上下文中都不使用。
  • mandant_uuid: 用户属于此客户。
  • username: 用户登录名
  • uuid: 公共可访问密钥
  • lastname
  • firstname
  • email
  • password_hash: 密码的盐化SHA3散列
  • salt: 类型4的uuid

sessions

如果用户成功登录,将包含session_key属性的用户的记录副本存储在会话集合中。

  • session_key: 类型4的uuid
  • session_created_at: 用于mongodb中的TTL的时间戳,设置为86400秒。这意味着数据库在一天后将从集合中删除会话。

会话密钥必须是唯一的。用户同一时间只能有一个会话。会话对象将在86400秒后被删除。

nestboxes

巢箱在概念上表示一个可引用的QR码。在我们的案例中,它将是一个挂在树上的木制巢箱。

  • _id: ObjectId
  • public: 是否巢箱数据是公开的 - true或false
  • uuid: 公共可访问密钥
  • mandant_uuid: 巢箱属于此客户
  • created_at: ISODate Zulu时间

geolocations

表示巢箱放置位置的地理定位随时间变化。

  • _id: ObjectId
  • uuid: 地理定位的公共密钥
  • nestbox_uuid: 地理定位附加的巢箱
  • from_date: 地理定位有效开始,Zulu时间戳
  • until_date: 地理定位有效结束,Zulu时间戳
  • position: 地理空间类型点

breeds

跟踪所有品种。

  • _id: ObjectId
  • uuid: 公共密钥
  • nestbox_uuid: 在此巢箱中发现的品种
  • user_uuid: 发现此品种的用户
  • discovery_date: 发现品种的时间戳Zulu时间
  • bird_uuid: 根据在箱中找到的巢估计的鸟类

birds

此集合存储了一个客户的所有鸟类。每个客户必须创建自己的鸟类。这种冗余的原因是

  • 不同的位置不同的鸟类
  • 不同的气候,不同的鸟类
  • 不同的海拔,不同的鸟类
  • 不同的语言,不同的鸟类

属性包括

  • _id: ObjectId
  • uuid: 鸟类的公共密钥
  • bird: 鸟类的名称
  • mandant_uuid: 创建鸟类的客户

Indices

数据库需要执行以下索引。

db.mandants.createIndex({"uuid": 1}, {"unique": true})
db.nestboxes.createIndex({"uuid": 1}, {"unique": true})
db.breeds.createIndex({"uuid": 1}, {"unique": true})
db.breeds.createIndex({"nestbox_uuid": 1})
db.users.createIndex({"uuid": 1}, {"unique": true})
db.users.createIndex({"username": 1}, {"unique": true})
db.geolocations.createIndex({"uuid": 1}, {"unique": true})
db.birds.createIndex({"uuid": 1}, {"unique": true})
db.birds.createIndex({"mandant_uuid": 1})
db.sessions.createIndex({"session_key": 2}, {"unique": true})
db.sessions.createIndex({"session_created_at": 1}, { expireAfterSeconds: 86400 })
db.geolocations.createIndex({"nestbox_uuid": 1})
db.nestboxes.createIndex({"mandant_uuid":1})

Backend

Framework

后端是基于actix-web编写的RESTful服务器。

Start

守护进程需要一个配置文件,如果没有提供配置文件则显示以下消息。

Usage: target/debug/nestboxd -c CONFIG_FILE

Options:
    -c, --config CONFIG_FILE
                        Path to configuration file

配置文件是一个YAML文件,内容如下

mongodb:
  uri: mongodb://localhost:27017
  database: nestbox
httpserver:
  ip: 127.0.0.1
  port: "8080"
images:
  directory: /some/where

Logging

目前有一个标准日志输出到STDOUT。

[2021-06-03T19:25:32Z INFO  actix_server::builder] Starting 4 workers
[2021-06-03T19:25:32Z INFO  actix_server::builder] Starting "actix-web-service-127.0.0.1:8080" service on 127.0.0.1:8080
[2021-06-03T19:29:25Z INFO  actix_web::middleware::logger] 127.0.0.1:47618 "GET /nestboxes/9915a1ef-edaa-4268-b86c-7e43fe0bbd6b/breeds?page_limit=2&page_number=1 HTTP/1.1" 200 539 "-" "curl/7.68.0" 0.024426
[2021-06-03T19:29:39Z INFO  actix_web::middleware::logger] 127.0.0.1:47624 "GET /nestboxes/9915a1ef-edaa-4268-b86c-7e43fe0bbd6b/breeds?page_limit=2&page_number=1 HTTP/1.1" 200 641 "-" "curl/7.68.0" 0.021756

post /login

允许登录。目前,database_bouncycastle对明文密码进行散列。这将被转换为散列,这意味着客户端只传输散列。如果用户登录两次,则旧的会话将被销毁。如果经过身份验证的用户登录失败,则当前会话也将被删除 - 这实际上意味着用户已被注销。

Request

curl \
  --header "Content-Type: application/json" \
  --request POST \
  --data '{"username":"fg_199","password":"secretbird"}' \
  http://127.0.0.1:8080/login

Response

{"username":"fg_199","success":true,"session":"28704470-0908-408e-938f-64dd2b7578b9"}

get /birds

Request

必须提供有效的会话。如果是这样,则用户将获得属于用户会话所属客户的鸟类可分页视图。

请注意,提供的鸟类是报告品种时可选择的鸟类。

curl -H "Authorization: Basic 2c91ebd1-800e-4573-8f2b-6ac91c2a407a" http://127.0.0.1:8080/birds?page_limit=2\&page_number=1

Response

{
   "documents": [
      {
         "uuid": "aee03da8-e297-46da-aac2-51f6604558dc",
         "bird": "bird_0"
      },
      {
         "uuid":"31a1e34e-7ae3-4b59-9197-c0ad9468fa20",
         "bird":"bird_1"
      }
   ],
   "counted_documents":150,
   "pages":75,
   "page_number":1,
   "page_limit":2
}

如果没有提供或提供的会话无效,则响应将是

{"error":2,"error_message":"UNAUTHORIZED"}

get /nestboxes/{uuid}/breeds

Request

curl \
  -H "Authorization: Basic 28704470-0908-408e-938f-64dd2b7578b9" \
  -H "Content-Type: application/json" \
  http://127.0.0.1:8080/nestboxes/9915a1ef-edaa-4268-b86c-7e43fe0bbd6b/breeds?page_limit=2\&page_number=1

Response

{
   "documents":[
      {
         "uuid":"0b5cec76-02ac-4c6e-933e-62ebfae3e337",
         "nestbox_uuid":"6f25fd00-011a-462f-aa3d-6959e6809017",
         "discovery_date":"2021-06-01 18:36:38.989 UTC",
         "user_uuid":"071f3391-2c8f-4807-89d8-4b2870228730",
         "bird_uuid":"ebe661d6-77ba-4bd1-bae3-9e4e7eb880a6",
         "bird":"bird_17"
      },
      {
         "uuid":"320d1b78-3e30-4741-aff2-ce8180dd09fb",
         "nestbox_uuid":"6f25fd00-011a-462f-aa3d-6959e6809017",
         "discovery_date":"2021-06-01 18:36:38.989 UTC",
         "user_uuid":"071f3391-2c8f-4807-89d8-4b2870228730",
         "bird_uuid":"afc7ffcf-92aa-4e2a-9c41-92eb300d0281",
         "bird":"bird_76"
      },
      {
         "uuid":"aae7236e-74fb-40cb-8502-111ac4f2d984",
         "nestbox_uuid":"6f25fd00-011a-462f-aa3d-6959e6809017",
         "discovery_date":"2021-06-01 18:36:38.989 UTC",
         "user_uuid":"071f3391-2c8f-4807-89d8-4b2870228730",
         "bird_uuid":"60e4a52c-cd81-44fc-90bc-b2b578caae08",
         "bird":"bird_108"
      }
   ],
   "counted_documents":3,
   "pages":1,
   "page_number":1,
   "page_limit":10
}

如果用户未经过身份验证,则响应将不带user_uuid。

{
   "documents":[
      {
         "uuid":"0b5cec76-02ac-4c6e-933e-62ebfae3e337",
         "nestbox_uuid":"6f25fd00-011a-462f-aa3d-6959e6809017",
         "discovery_date":"2021-06-01 18:36:38.989 UTC",
         "bird_uuid":"ebe661d6-77ba-4bd1-bae3-9e4e7eb880a6",
         "bird":"bird_17"
      },
      {
         "uuid":"320d1b78-3e30-4741-aff2-ce8180dd09fb",
         "nestbox_uuid":"6f25fd00-011a-462f-aa3d-6959e6809017",
         "discovery_date":"2021-06-01 18:36:38.989 UTC",
         "bird_uuid":"afc7ffcf-92aa-4e2a-9c41-92eb300d0281",
         "bird":"bird_76"
      },
      {
         "uuid":"aae7236e-74fb-40cb-8502-111ac4f2d984",
         "nestbox_uuid":"6f25fd00-011a-462f-aa3d-6959e6809017",
         "discovery_date":"2021-06-01 18:36:38.989 UTC",
         "bird_uuid":"60e4a52c-cd81-44fc-90bc-b2b578caae08",
         "bird":"bird_108"
      }
   ],
   "counted_documents":3,
   "pages":1,
   "page_number":1,
   "page_limit":10
}

post /nestboxes/{uuid}/breeds

Request

curl \
  -H "Authorization: Basic b955d5ab-531d-45a5-b610-5b456fa509d9" \
  --H "Content-Type: application/json" \
  --request POST \
  --data '{"bird_uuid": "a4152a25-b734-4748-8a43-2401ed387c65", "bird":"a"}' \
  http://127.0.0.1:8080/nestboxes/9973e59f-771d-452f-9a1b-8b4a6d5c4f95/breeds

Response

{"inserted_id":{"$oid":"60bfcc160014769d00e0b88a"}}

post /nestboxes/{uuid}/geolocations

Request

向巢箱添加新的地理位置。如果巢箱被移动(例如,树被砍伐或损坏),则属于这种情况。

curl   \
  -H "Authorization: Basic 8f42f009-dda8-4448-a2db-f9abb8326b06" \
  -H "Content-Type: application/json"   \
  --request POST   \
  --data '{ "long":-11.6453, "lat": -47.2345}'   \
  http://127.0.0.1:8080/nestboxes/787c9399-b10a-44f7-bcc5-251e4414cbb0/geolocations

Response

如果一切顺利

{"inserted_id":"ObjectId(\"60d5a4ed0062a0f1006a1967\")"}

如果用户未正确认证

{"error":2,"error_message":"UNAUTHORIZED"}

或者巢箱属于另一个客户,则用户会话

{"error":1,"error_message":"NESTBOX_OF_OTHER_MANDANT"}

get /nestboxes/{uuid}

Request

检索巢箱。

curl  http://127.0.0.1:8080/nestboxes/1bec20fc-5416-4941-b7e4-e15aa26a5c7a

Response

{
  "uuid": "5d04fab4-6bdd-4103-b57f-9e231e777790",
  "created_at": "2021-06-01 18:36:38.443 +00:00:00",
  "images": [
    "935206e4bf27eccfa4562f32043ef9435c0a037dfd08811817c187ed618412c1.jpg"
  ],
  "mandant_uuid": "a9ed6720-5e01-417a-9278-9417df4d8aa0",
  "mandant_name": "BirdLife 100",
  "mandant_website": "https://www.birdwatcher.ch"
}

post /nestboxes/{uuid}/images

Request

允许发布巢箱的图片。

curl -v   -H "Authorization: Basic 9e693578-500c-4505-990a-5db978e557c2"     -F file=@20211024_170535.jpg  http://127.0.0.1:8080/nestboxes/5d04fab4-6bdd-4103-b57f-9e231e777790/images

Response

如果已认证并授权,则返回文件的SHA3哈希值。

< HTTP/1.1 201 Created
< content-length: 86
< content-type: application/json
< date: Thu, 09 Dec 2021 18:03:50 GMT
< 

{"file_name":["c9aff3597f2fbc4dd5a22c9c0764c2324c5dd68776a367ac150e6a40bfed6526.jpg"]}

如果未认证或未授权

< HTTP/1.1 401 Unauthorized
< content-length: 42
< content-type: application/json
< date: Thu, 09 Dec 2021 18:06:40 GMT
* HTTP error before end of send, stop sending
< 
* Closing connection 0
{"error":2,"error_message":"UNAUTHORIZED"}

依赖关系

~66MB
~1M SLoC