{"id":41501074,"url":"https://github.com/sdoerig/nestbox","last_synced_at":"2026-01-23T19:06:59.568Z","repository":{"id":44867925,"uuid":"348069083","full_name":"sdoerig/nestbox","owner":"sdoerig","description":"Nestbox and breed tracker for birders.","archived":false,"fork":false,"pushed_at":"2022-07-29T19:32:32.000Z","size":7465,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-01-26T08:45:47.655Z","etag":null,"topics":["actix","bird","nestbox","rust","webservice"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sdoerig.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-03-15T17:49:08.000Z","updated_at":"2023-01-12T12:39:25.000Z","dependencies_parsed_at":"2022-08-29T23:01:01.028Z","dependency_job_id":null,"html_url":"https://github.com/sdoerig/nestbox","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/sdoerig/nestbox","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sdoerig%2Fnestbox","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sdoerig%2Fnestbox/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sdoerig%2Fnestbox/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sdoerig%2Fnestbox/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sdoerig","download_url":"https://codeload.github.com/sdoerig/nestbox/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sdoerig%2Fnestbox/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28698366,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-23T17:25:48.045Z","status":"ssl_error","status_checked_at":"2026-01-23T17:25:47.153Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["actix","bird","nestbox","rust","webservice"],"created_at":"2026-01-23T19:06:58.764Z","updated_at":"2026-01-23T19:06:59.555Z","avatar_url":"https://github.com/sdoerig.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# nestbox\n\n## Note\n\nI'm still developing. My aim is to create a minimum viable product. This means\n\n- Having a web based GUI (missing)\n- Having a backend, which is able to\n  - show data according to the scanned QR code.\n  - authenticate a user\n  - accept modifications of nestboxes by an authenticated and authorized user\n\n\n## Idea\n\nSome birdwatcher are also taking care of nestboxes. So these nestboxes are regularly checked during wintertime. To give you an idea, we're taking care of nearly 400 boxes. \nIf a bird has been using the nestbox one can distinguish the specific bird by the nest built. Then the birdwatcher cleans the box, which means the nest is removed and the box is ready for the next breeding. Normaly there is also a list keept, which nestboxes where used by which birds and which year. So my intention is to give the nestbox cleaner a tool as today nearly everybody has a smartphone available. So the key thoughts are:\n\n- A QR code tag on each nestbox.\n- A authenticaated cleaner now can scan the QR code and perform the follwing action.\n  - Set a new geolocation for the box, so it can be found later.\n  - Set a detected breeding (type of bird and year)\n- Somebody just walking by can also scan the QR code an gets the following informations:\n  - Association responsible for the box and how to contact them.\n  - History of the box, breedings in the past.\n- Getting the associations taking care of those boxes \"out of the dark\", since they do valuable protection work and many people do not know the even exist.\n- Personally I find it fun developing this kind of application beside work. Any backend like software is written in Rust - not because I'm exceptionally good at Rust, but I like it and I like learning too.\n\n## Repo\nCurrently it is a multipart repo containing the parts\n\n- dateabase_bouncycastle\n  - This is uses to stock the database (mongodb) with data, since it can be bothersome to develop something on an empty database.\n  \n- nestboxd\n  - Contains the backend server part.\n\n## database_bouncycastle\nAs written above, this is only to fill up the database with some example data, \n```\nUsage: target/release/database_bouncycastle -m mongodb://127.0.0.1:27017/?w=majority -d nestbox_bouncycastle -n 123\n\nOptions:\n    -m, --mongodb_host MONGO_DB_HOST\n                        URI to mongodb e.g mongodb://\u003cdb_host\u003e:\u003cdb_port\u003e/\n    -d, --database_name MONGO_DB_HOST\n                        dateabase name e.g. nestbox_bouncycastle\n    -n, --number of records to insert NUMBER\n                        The number of records to insert\n```\nE.g. if executed with -n 12 the follwing number of records are inserted:\n\n- 2 records in the collestion mandants\n- 12 records in the collection nestboxes\n- 12 records in the collection users\n- 72 records in the collection geolocations\n- 72 records in the collection breeds\n- 300 records in the collestion birds\n\nAs written above, database_bouncycastle is only to stock the database with example data. A reasonable nomber of nestboxes are 10 millions, so one can see at a glance where indices are missing. Each 100 nextboxes a new mandant will be generated holding 100 nestboxes. Each mandant owns 150 birds, each nestbox has 6 geolocations and 6 breeds.\n\n## Database model\n\n### General\n\nAllthough mongodb is a schemaless database, the concept of the model can described easy. Note it is just a skeleton, which means I just included the attributs I really consider to tbe vital. \n\n### uuid\n\nIn each record one finds an attribut `uuid`. It acts as public visible primary key, since I think on should not unveil to much about the database and mongodb object ids are guessable. So the uuid is randomly generated. Any field within a collection named \"uuid\" is a public accessable key and can be used in URLs an so on. They all are randomly generated type 4 uuids.\n\n### Model\n\nBefore going into details of each collection here is a quick overview ot the relations of the collections.\n\n```\n\nmandant 1 ----- n users\n|\n+----- 1 ------ n nestboxes 1 ----- n breeds\n                  |\n                  \\ 1 ------------- n geolocation\n\n```\n\n### mandants\n\nA mandant is an association of birdwatchers if you want so. They are taking care of a cuple of birdboxes in a defined geografical area. One record holds now the attributes\n\n- _id: ObjectId - not used in any context now.\n- uuid: Public accessable key e.g. in URLs. Example e7620353-b6f6-47e9-b543-66af20769145\n- name: Name of the association, E.g. BirdLife \n- website: Most of these associations do have a website e.g. https://www.birdwatcher.ch\n- email: Obvious e.g. bird@iseeyou.ch \n\n\n### users\n\nA user belongs to a mandant and can mutate any birdbox belonging tho this mandant. Currently a simple password based authentication is implemented. The attributs:\n\n- _id: ObjectId - not used in any context now. \n- mandant_uuid: user belongs to this mandant. \n- username: login name of the user  \n- uuid: Public accessable key \n- lastname\n- firstname\n- email\n- password_hash: Salted SHA3 hash of the password\n- salt: Type 4 uuid\n\n### sessions\n\nIf a user successfully logged in a copy of the user record with the attribute session_key is stored in the sessions collections.\n\n- session_key: uuid type 4\n- session_created_at: timestamp used for a TTL within mongodb, set to 86400 seconds. Means the database removes the session form the collection after one day.\n\nThe session key must be uniqe. A user can only have one session at the same time. The session object will be deleted after 86400 seconds.\n\n### nestboxes\n\nA nestbox represents by concept a QR code referencable item. In our case it would be a wooden nestbox e.g. hanging in a tree.\n\n- _id: ObjectId \n- public: Is the nestbox data public - true or false \n- uuid: Public accessable key\n- mandant_uuid: Nestbox belongs to this mandant \n- created_at: ISODate Zulu time\n\n### geolocations \n\nGeolocation indicating where the nestbox was placed over the time.\n\n- _id: ObjectId\n- uuid: Public key of the geolocation \n- nestbox_uuid: nestbox the location is attached to\n- from_date: Geolocation valid from, timestamp Zulu time\n- until_date: Geolocation valid until, timestamp Zulu time\n- position: Geospatial type point \n\n### breeds\n\nHolds track of all the breeds.\n\n- _id: ObjectId\n- uuid: Public key \n- nestbox_uuid: Breed discovered in this nestbox\n- user_uuid: Breed discoverd by this user \n- discovery_date: Breed discovered at timestamp Zulu time\n- bird_uuid: Estimated bird according to the nest found in the box\n\n### birds\n\nThis collection stores all the birds of one mandant. Each mandant must create its own birds. The reasons for this redundancy are\n\n- different location different birds\n- different clima, different birds\n- different altitude, different birds\n- different language, different birds\n\nThe attributes are\n\n- _id: ObjectId\n- uuid: Public key of the bird\n- bird: Name of the bird\n- mandant_uuid: Mandant by which the bird was created\n\n## Indices\n\nThe database needs to perform the indices below.\n\n```\ndb.mandants.createIndex({\"uuid\": 1}, {\"unique\": true})\ndb.nestboxes.createIndex({\"uuid\": 1}, {\"unique\": true})\ndb.breeds.createIndex({\"uuid\": 1}, {\"unique\": true})\ndb.breeds.createIndex({\"nestbox_uuid\": 1})\ndb.users.createIndex({\"uuid\": 1}, {\"unique\": true})\ndb.users.createIndex({\"username\": 1}, {\"unique\": true})\ndb.geolocations.createIndex({\"uuid\": 1}, {\"unique\": true})\ndb.birds.createIndex({\"uuid\": 1}, {\"unique\": true})\ndb.birds.createIndex({\"mandant_uuid\": 1})\ndb.sessions.createIndex({\"session_key\": 2}, {\"unique\": true})\ndb.sessions.createIndex({\"session_created_at\": 1}, { expireAfterSeconds: 86400 })\ndb.geolocations.createIndex({\"nestbox_uuid\": 1})\ndb.nestboxes.createIndex({\"mandant_uuid\":1})\n```\n\n## Backend\n\n### Framework\nThe backend is a restful server written based on [actix-web](https://actix.rs/).\n\n### Start \n\nThe daeamon needs a config file, if started without one gets the message below.\n\n```\nUsage: target/debug/nestboxd -c CONFIG_FILE\n\nOptions:\n    -c, --config CONFIG_FILE\n                        Path to configuration file\n\n```\n\nThe config file is a YAML file with the content\n\n```\nmongodb:\n  uri: mongodb://localhost:27017\n  database: nestbox\nhttpserver:\n  ip: 127.0.0.1\n  port: \"8080\"\n```\n\n### Logging\n\nThere is at the moment a standard logging to STDOUT.\n\n```\n[2021-06-03T19:25:32Z INFO  actix_server::builder] Starting 4 workers\n[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\n[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\u0026page_number=1 HTTP/1.1\" 200 539 \"-\" \"curl/7.68.0\" 0.024426\n[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\u0026page_number=1 HTTP/1.1\" 200 641 \"-\" \"curl/7.68.0\" 0.021756\n```\n\n\n### post /login\n\nAllows login. At the moment, database_bouncycastle hashes clear text passwords. This will be turned into hashes, which means then the client transmits only the hash.\nIf a user loges in twice, the old session is destroyed. If an authenticated user fails to login the current session is deleted too - which actually means the user has been logged out.\n\n#### Request\n\n```\ncurl \\\n  --header \"Content-Type: application/json\" \\\n  --request POST \\\n  --data '{\"username\":\"fg_199\",\"password\":\"secretbird\"}' \\\n  http://127.0.0.1:8080/login\n```\n#### Response\n\n```\n{\"username\":\"fg_199\",\"success\":true,\"session\":\"28704470-0908-408e-938f-64dd2b7578b9\"}\n```\n\n\n### get /birds\n\n#### Request\n\nA valid session must be provided. If so the users gets a pageable view of the birds beloging to the mandant the user session belongs to.\n\nNote also the birds provided are the ones selectable when reporting a breed.\n\n```\ncurl -H \"Authorization: Basic 2c91ebd1-800e-4573-8f2b-6ac91c2a407a\" http://127.0.0.1:8080/birds?page_limit=2\\\u0026page_number=1\n```\n\n#### Response\n\n```\n{\n   \"documents\": [\n      {\n         \"uuid\": \"aee03da8-e297-46da-aac2-51f6604558dc\",\n         \"bird\": \"bird_0\"\n      },\n      {\n         \"uuid\":\"31a1e34e-7ae3-4b59-9197-c0ad9468fa20\",\n         \"bird\":\"bird_1\"\n      }\n   ],\n   \"counted_documents\":150,\n   \"pages\":75,\n   \"page_number\":1,\n   \"page_limit\":2\n}\n```\n\nIf no or an invalid session is provided the response will be \n\n```\n{\"error\":2,\"error_message\":\"UNAUTHORIZED\"}\n```\n\n\n\n### get /nestboxes/{uuid}/breeds\n\n#### Request\n\n```\ncurl \\\n  -H \"Authorization: Basic 28704470-0908-408e-938f-64dd2b7578b9\" \\\n  -H \"Content-Type: application/json\" \\\n  http://127.0.0.1:8080/nestboxes/9915a1ef-edaa-4268-b86c-7e43fe0bbd6b/breeds?page_limit=2\\\u0026page_number=1\n\n```\n\n#### Response\n\n```\n{\n   \"documents\":[\n      {\n         \"uuid\":\"0b5cec76-02ac-4c6e-933e-62ebfae3e337\",\n         \"nestbox_uuid\":\"6f25fd00-011a-462f-aa3d-6959e6809017\",\n         \"discovery_date\":\"2021-06-01 18:36:38.989 UTC\",\n         \"user_uuid\":\"071f3391-2c8f-4807-89d8-4b2870228730\",\n         \"bird_uuid\":\"ebe661d6-77ba-4bd1-bae3-9e4e7eb880a6\",\n         \"bird\":\"bird_17\"\n      },\n      {\n         \"uuid\":\"320d1b78-3e30-4741-aff2-ce8180dd09fb\",\n         \"nestbox_uuid\":\"6f25fd00-011a-462f-aa3d-6959e6809017\",\n         \"discovery_date\":\"2021-06-01 18:36:38.989 UTC\",\n         \"user_uuid\":\"071f3391-2c8f-4807-89d8-4b2870228730\",\n         \"bird_uuid\":\"afc7ffcf-92aa-4e2a-9c41-92eb300d0281\",\n         \"bird\":\"bird_76\"\n      },\n      {\n         \"uuid\":\"aae7236e-74fb-40cb-8502-111ac4f2d984\",\n         \"nestbox_uuid\":\"6f25fd00-011a-462f-aa3d-6959e6809017\",\n         \"discovery_date\":\"2021-06-01 18:36:38.989 UTC\",\n         \"user_uuid\":\"071f3391-2c8f-4807-89d8-4b2870228730\",\n         \"bird_uuid\":\"60e4a52c-cd81-44fc-90bc-b2b578caae08\",\n         \"bird\":\"bird_108\"\n      }\n   ],\n   \"counted_documents\":3,\n   \"pages\":1,\n   \"page_number\":1,\n   \"page_limit\":10\n}\n```\nIf the user is not authenticated the response is without user_uuid.\n\n```\n{\n   \"documents\":[\n      {\n         \"uuid\":\"0b5cec76-02ac-4c6e-933e-62ebfae3e337\",\n         \"nestbox_uuid\":\"6f25fd00-011a-462f-aa3d-6959e6809017\",\n         \"discovery_date\":\"2021-06-01 18:36:38.989 UTC\",\n         \"bird_uuid\":\"ebe661d6-77ba-4bd1-bae3-9e4e7eb880a6\",\n         \"bird\":\"bird_17\"\n      },\n      {\n         \"uuid\":\"320d1b78-3e30-4741-aff2-ce8180dd09fb\",\n         \"nestbox_uuid\":\"6f25fd00-011a-462f-aa3d-6959e6809017\",\n         \"discovery_date\":\"2021-06-01 18:36:38.989 UTC\",\n         \"bird_uuid\":\"afc7ffcf-92aa-4e2a-9c41-92eb300d0281\",\n         \"bird\":\"bird_76\"\n      },\n      {\n         \"uuid\":\"aae7236e-74fb-40cb-8502-111ac4f2d984\",\n         \"nestbox_uuid\":\"6f25fd00-011a-462f-aa3d-6959e6809017\",\n         \"discovery_date\":\"2021-06-01 18:36:38.989 UTC\",\n         \"bird_uuid\":\"60e4a52c-cd81-44fc-90bc-b2b578caae08\",\n         \"bird\":\"bird_108\"\n      }\n   ],\n   \"counted_documents\":3,\n   \"pages\":1,\n   \"page_number\":1,\n   \"page_limit\":10\n}\n```\n\n\n### post /nestboxes/{uuid}/breeds\n\n#### Request\n\n```\ncurl \\\n  -H \"Authorization: Basic b955d5ab-531d-45a5-b610-5b456fa509d9\" \\\n  --H \"Content-Type: application/json\" \\\n  --request POST \\\n  --data '{\"bird_uuid\": \"a4152a25-b734-4748-8a43-2401ed387c65\", \"bird\":\"a\"}' \\\n  http://127.0.0.1:8080/nestboxes/9973e59f-771d-452f-9a1b-8b4a6d5c4f95/breeds\n\n```\n\n\n#### Response\n\n```\n{\"inserted_id\":{\"$oid\":\"60bfcc160014769d00e0b88a\"}}\n```\n\n### post /nestboxes/{uuid}/geolocations\n\n#### Request\n\nAdds a new geolocation to a nestbox. This is the case if it had been moved e.g. tree has been cut or broken down.\n\n```\ncurl   \\\n  -H \"Authorization: Basic 8f42f009-dda8-4448-a2db-f9abb8326b06\" \\\n  -H \"Content-Type: application/json\"   \\\n  --request POST   \\\n  --data '{ \"long\":-11.6453, \"lat\": -47.2345}'   \\\n  http://127.0.0.1:8080/nestboxes/787c9399-b10a-44f7-bcc5-251e4414cbb0/geolocations\n\n```\n\n#### Response\n\nIn case everything went well\n\n```\n{\"inserted_id\":\"ObjectId(\\\"60d5a4ed0062a0f1006a1967\\\")\"}\n```\n\nIf the user was not properly authenticated\n\n```\n{\"error\":2,\"error_message\":\"UNAUTHORIZED\"}\n```\nor if the nestbox belongs to another mandant then the user session\n\n```\n{\"error\":1,\"error_message\":\"NESTBOX_OF_OTHER_MANDANT\"}\n```\n\n### get /nestboxes/{uuid}\n\n#### Request\nFetches a nestbox.\n```\ncurl  http://127.0.0.1:8080/nestboxes/1bec20fc-5416-4941-b7e4-e15aa26a5c7a\n```\n\n#### Response\n\n```\n{\"_id\":{\"$oid\":\"60b67e360047576800f56ba1\"},\"public\":true,\"uuid\":\"1bec20fc-5416-4941-b7e4-e15aa26a5c7a\",\"mandant_uuid\":\"c7d880d5-c98d-40ee-bced-b5a0165420c0\",\"created_at\":{\"$date\":\"2021-06-01T18:36:38.418Z\"}}\n``` \n\n### post /nestboxes/{uuid}/images\n\n#### Request\nAllows to post an image of the testbox.\n```\ncurl -v   -H \"Authorization: Basic 3c88c048-8e06-4332-ae11-c82cd991a383\"   -H \"Content-Type: application/json\"   --request POST   -F secret=@../../../Nextcloud/SofortUpload/Camera/20211024_170535.jpg   http://127.0.0.1:8080/nestboxes/140a3916-a2d8-4ca9-bae8-7e3c15428e8f/images\n```\n\n#### Response\n\nIf authenticated and authorized a SHA3 hash of the file is returned.\n\n```\n\u003c HTTP/1.1 201 Created\n\u003c content-length: 86\n\u003c content-type: application/json\n\u003c date: Thu, 09 Dec 2021 18:03:50 GMT\n\u003c \n\n{\"file_name\":[\"c9aff3597f2fbc4dd5a22c9c0764c2324c5dd68776a367ac150e6a40bfed6526.jpg\"]}\n```\n\nIf not authenticated or authorized \n\n```\n\u003c HTTP/1.1 401 Unauthorized\n\u003c content-length: 42\n\u003c content-type: application/json\n\u003c date: Thu, 09 Dec 2021 18:06:40 GMT\n* HTTP error before end of send, stop sending\n\u003c \n* Closing connection 0\n{\"error\":2,\"error_message\":\"UNAUTHORIZED\"}\n```\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsdoerig%2Fnestbox","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsdoerig%2Fnestbox","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsdoerig%2Fnestbox/lists"}