📄 LibraryRegistryStorage.sol
D-OPEN SOVEREIGN
1/*
2 * Copyright (c) 2026 DataPond D-Library Pty Ltd. ("The Sanctuary")
3 * Non-Profit Public Library — The World Library
4 *
5 * D-SAFE Certification issued by POND Enterprise.
6 * Published under the D-Open Code Sovereign Licence v1.0 framework
7 * DataPond D-Library All exclusive Right reserved on modifiying the code - CC Attribution to data pond, Non modifiable, Non Commercial.
8 * https://registry.world.bibliotech.com/licence
9 * Source code donated to DataPond D-Library by it's author: data pond.
10 *
11 * Technical Guardian ("The Shield"): Pond Enterprise Pty Ltd. (ACN 694 747 987)
12 * All liability claims about D-SAFE certification issues coming from the published D-Safe direction must be addressed with Pond Enterprise Pty Ltd.
13 * Code Modification automatically voids the certified liability protection provided by Pond Enterprise.
14 */
15
16// SPDX-License-Identifier: Apache-2.0
17pragma solidity ^0.8.24;
18
19import {ErrorLibrary} from "./ErrorLibrary.sol";
20
21interface IPublicLibraryStorage {
22    function libraryExists(uint256 id) external view returns (bool);
23    function formatExists(string calldata format) external view returns (bool);
24}
25
26contract LibraryRegistryStorage {
27
28    enum LibraryType {
29        Online,
30        PhysicalWithDigitalCopies
31    }
32
33    enum ImportStatus {
34        Pending,
35        InProgress,
36        Completed,
37        Failed
38    }
39
40    struct Collection {
41        string id;
42        string slug;
43        string manifestUrl;
44        string importerUsername;
45        address owner;
46        bool verified;
47        uint256 fileCount;
48        LibraryType libraryType;
49        uint256 publicLibraryId; // 0 = not linked to a PublicLibraryStorage entry
50        string language;
51        bool isPublic;
52        uint64 createdAt;
53        uint64 updatedAt;
54        uint256 initialBookCount;
55        uint256 dSafeBookCount;
56        ImportStatus importStatus;
57        bool published;
58        string preferredFormat;
59    }
60
61    struct CollectionInput {
62        string id;
63        string slug;
64        string manifestUrl;
65        string importerUsername;
66        address owner;
67        uint256 fileCount;
68        LibraryType libraryType;
69        uint256 publicLibraryId; // 0 = not linked to a PublicLibraryStorage entry
70        string language;
71        bool isPublic;
72        uint256 initialBookCount;
73        uint256 dSafeBookCount;
74        string preferredFormat;
75    }
76
77    // State Variables
78    address public admin; // single admin or multi-sig if you choose
79    mapping(address => bool) public _isAuthorized; // reserved for future write access (unused for now)
80    IPublicLibraryStorage public publicLibraryStorage;
81
82    // Collection Storage
83    mapping(string => Collection) private collections;
84
85    string[] private unverifiedCollectionIds;
86    string[] private verifiedCollectionIds;
87
88    event AuthorizedAdded(address indexed caller);
89    event AuthorizedRemoved(address indexed caller);
90
91    // Modifiers
92    modifier onlyAdmin() {
93        require(msg.sender == admin, "not admin");
94        _;
95    }
96
97    modifier onlyAuthorized() {
98        require(_isAuthorized[msg.sender], "not authorized");
99        _;
100    }
101
102    constructor() {
103        admin = msg.sender;
104    }
105
106    // ===== Admin / authorization (reserved for future write access) =====
107    function isAuthorized(address caller) public view returns (bool) {
108        require(caller != address(0), "zero addr");
109        return _isAuthorized[caller];
110    }
111
112    function addAuthorized(address caller) public onlyAdmin {
113        require(caller != address(0), "zero addr");
114        _isAuthorized[caller] = true;
115        emit AuthorizedAdded(caller);
116    }
117
118    function removeAuthorized(address caller) public onlyAdmin {
119        _isAuthorized[caller] = false;
120        emit AuthorizedRemoved(caller);
121    }
122
123    function setPublicLibraryStorage(address storageAddr) external onlyAdmin {
124        if (storageAddr == address(0)) revert ErrorLibrary.EmptyAddress();
125        publicLibraryStorage = IPublicLibraryStorage(storageAddr);
126    }
127
128    // 0 means "not linked" and is always allowed; a non-zero id must exist in PublicLibraryStorage.
129    function _validatePublicLibraryId(uint256 publicLibraryId) internal view {
130        if (publicLibraryId == 0) return;
131        if (address(publicLibraryStorage) == address(0) || !publicLibraryStorage.libraryExists(publicLibraryId)) {
132            revert ErrorLibrary.InvalidPublicLibraryId();
133        }
134    }
135
136    function _validateFormat(string calldata format) internal view {
137        if (address(publicLibraryStorage) == address(0) || !publicLibraryStorage.formatExists(format)) {
138            revert ErrorLibrary.InvalidFormat();
139        }
140    }
141
142    // Public Submission — admin-only for now (Called by Orchestrator on Arweave Upload Success)
143    function addUnverifiedCollection(CollectionInput calldata input) external onlyAdmin {
144        if (bytes(collections[input.id].id).length != 0) {
145            revert ErrorLibrary.ErrAlreadyRegistered();
146        }
147        _validatePublicLibraryId(input.publicLibraryId);
148        _validateFormat(input.preferredFormat);
149
150        uint64 nowTs = uint64(block.timestamp);
151
152        collections[input.id] = Collection({
153            id: input.id,
154            slug: input.slug,
155            manifestUrl: input.manifestUrl,
156            importerUsername: input.importerUsername,
157            owner: input.owner,
158            verified: false,
159            fileCount: input.fileCount,
160            libraryType: input.libraryType,
161            publicLibraryId: input.publicLibraryId,
162            language: input.language,
163            isPublic: input.isPublic,
164            createdAt: nowTs,
165            updatedAt: nowTs,
166            initialBookCount: input.initialBookCount,
167            dSafeBookCount: input.dSafeBookCount,
168            importStatus: ImportStatus.Pending,
169            published: false,
170            preferredFormat: input.preferredFormat
171        });
172
173        unverifiedCollectionIds.push(input.id);
174    }
175
176    // Admin-only Read Operations (Access Control Restricted)
177    function getUnverifiedCollectionIds() external view onlyAdmin returns (string[] memory) {
178        return unverifiedCollectionIds;
179    }
180
181    // Public Read Operations
182    function getVerifiedCollectionIds() external view returns (string[] memory) {
183        return verifiedCollectionIds;
184    }
185
186    function getCollection(string calldata _id) external view returns (Collection memory) {
187        Collection memory c = collections[_id];
188        // Enforce that unverified collections are only viewable by the admin or the owner
189        if (!c.verified && msg.sender != admin && c.owner != msg.sender) {
190            revert ErrorLibrary.Forbidden(msg.sender);
191        }
192        return c;
193    }
194
195    // Manual Verification Workflow (admin-only for now)
196    function verifyCollection(string calldata _id) external onlyAdmin {
197        Collection storage c = collections[_id];
198        if (bytes(c.id).length == 0) revert ErrorLibrary.InvalidProjectId();
199        if (c.verified) revert ErrorLibrary.ErrAlreadyRegistered();
200
201        c.verified = true;
202        c.updatedAt = uint64(block.timestamp);
203        verifiedCollectionIds.push(_id);
204
205        // Remove from unverified list
206        for (uint256 i = 0; i < unverifiedCollectionIds.length; i++) {
207            if (keccak256(bytes(unverifiedCollectionIds[i])) == keccak256(bytes(_id))) {
208                unverifiedCollectionIds[i] = unverifiedCollectionIds[unverifiedCollectionIds.length - 1];
209                unverifiedCollectionIds.pop();
210                break;
211            }
212        }
213    }
214
215    function rejectCollection(string calldata _id) external onlyAdmin {
216        Collection memory c = collections[_id];
217        if (bytes(c.id).length == 0) revert ErrorLibrary.InvalidProjectId();
218        if (c.verified) revert ErrorLibrary.ErrAlreadyRegistered();
219
220        delete collections[_id];
221
222        // Remove from unverified list
223        for (uint256 i = 0; i < unverifiedCollectionIds.length; i++) {
224            if (keccak256(bytes(unverifiedCollectionIds[i])) == keccak256(bytes(_id))) {
225                unverifiedCollectionIds[i] = unverifiedCollectionIds[unverifiedCollectionIds.length - 1];
226                unverifiedCollectionIds.pop();
227                break;
228            }
229        }
230    }
231
232    // ===== Lifecycle updates (admin-only for now) =====
233    function updateCollectionStats(
234        string calldata _id,
235        uint256 initialBookCount,
236        uint256 dSafeBookCount
237    ) external onlyAdmin {
238        Collection storage c = collections[_id];
239        if (bytes(c.id).length == 0) revert ErrorLibrary.InvalidProjectId();
240
241        c.initialBookCount = initialBookCount;
242        c.dSafeBookCount = dSafeBookCount;
243        c.updatedAt = uint64(block.timestamp);
244    }
245
246    function setImportStatus(string calldata _id, ImportStatus status) external onlyAdmin {
247        Collection storage c = collections[_id];
248        if (bytes(c.id).length == 0) revert ErrorLibrary.InvalidProjectId();
249
250        c.importStatus = status;
251        c.updatedAt = uint64(block.timestamp);
252    }
253
254    function setPublished(string calldata _id, bool isPublished) external onlyAdmin {
255        Collection storage c = collections[_id];
256        if (bytes(c.id).length == 0) revert ErrorLibrary.InvalidProjectId();
257
258        c.published = isPublished;
259        c.updatedAt = uint64(block.timestamp);
260    }
261
262    function setCollectionPublicLibrary(string calldata _id, uint256 publicLibraryId) external onlyAdmin {
263        Collection storage c = collections[_id];
264        if (bytes(c.id).length == 0) revert ErrorLibrary.InvalidProjectId();
265        _validatePublicLibraryId(publicLibraryId);
266
267        c.publicLibraryId = publicLibraryId;
268        c.updatedAt = uint64(block.timestamp);
269    }
270}
271