// SPDX-License-Identifier: MIT pragma solidity ^0.8.28; /// @title CommunityNeedsBoard /// @notice A non-payable board of bounded community needs. Any caller may submit a /// request (bounded title/description), endorse each request at most once /// per caller, and attach or update a bounded reference to an accepted /// task or app. No fund handling of any kind. contract CommunityNeedsBoard { uint256 public constant MAX_TITLE_LEN = 80; uint256 public constant MAX_DESCRIPTION_LEN = 600; uint256 public constant MAX_LINKED_WORK_LEN = 160; uint256 public constant MAX_REQUESTS = 10000; struct NeedRequest { address submitter; string title; string description; string linkedWork; // bounded reference to an accepted task or app; "" = none bool resolved; uint256 endorsements; uint256 submittedAt; } NeedRequest[] private requests; mapping(uint256 => mapping(address => bool)) private endorsedBy; event RequestSubmitted(uint256 indexed requestId, address indexed submitter, string title); event RequestEndorsed(uint256 indexed requestId, address indexed endorser, uint256 endorsements); event RequestResolved(uint256 indexed requestId, string linkedWork); event LinkedWorkUpdated(uint256 indexed requestId, string linkedWork); /// @notice Submit a new need with a bounded title and description. Returns its id. function submitRequest(string calldata title, string calldata description) external returns (uint256 requestId) { require(bytes(title).length > 0 && bytes(title).length <= MAX_TITLE_LEN, "title: 1-80 chars"); require(bytes(description).length <= MAX_DESCRIPTION_LEN, "description: <=600 chars"); require(requests.length < MAX_REQUESTS, "board full"); requests.push( NeedRequest({ submitter: msg.sender, title: title, description: description, linkedWork: "", resolved: false, endorsements: 0, submittedAt: block.timestamp }) ); requestId = requests.length - 1; emit RequestSubmitted(requestId, msg.sender, title); } /// @notice Endorse a request, at most once per caller. function endorseRequest(uint256 requestId) external { require(requestId < requests.length, "unknown request"); require(!endorsedBy[requestId][msg.sender], "already endorsed"); endorsedBy[requestId][msg.sender] = true; requests[requestId].endorsements += 1; emit RequestEndorsed(requestId, msg.sender, requests[requestId].endorsements); } /// @notice Attach or update the bounded accepted-task/app reference (not a resolution). function setLinkedWork(uint256 requestId, string calldata linkedWork) external { require(requestId < requests.length, "unknown request"); require(bytes(linkedWork).length <= MAX_LINKED_WORK_LEN, "linkedWork: <=160 chars"); requests[requestId].linkedWork = linkedWork; emit LinkedWorkUpdated(requestId, linkedWork); } /// @notice Mark a request resolved, pointing at the accepted task or app. function resolveRequest(uint256 requestId, string calldata linkedWork) external { require(requestId < requests.length, "unknown request"); require( bytes(linkedWork).length > 0 && bytes(linkedWork).length <= MAX_LINKED_WORK_LEN, "linkedWork: 1-160 chars" ); NeedRequest storage r = requests[requestId]; r.linkedWork = linkedWork; r.resolved = true; emit RequestResolved(requestId, linkedWork); } /// @notice Number of submitted requests. function requestCount() external view returns (uint256) { return requests.length; } /// @notice Indexed read of one request. function getRequest(uint256 requestId) external view returns ( address submitter, string memory title, string memory description, string memory linkedWork, bool resolved, uint256 endorsements, uint256 submittedAt ) { require(requestId < requests.length, "unknown request"); NeedRequest storage r = requests[requestId]; return ( r.submitter, r.title, r.description, r.linkedWork, r.resolved, r.endorsements, r.submittedAt ); } /// @notice Indexed read of whether a caller endorsed a request. function hasEndorsed(uint256 requestId, address caller) external view returns (bool) { require(requestId < requests.length, "unknown request"); return endorsedBy[requestId][caller]; } }