psibase/services/sites.rs
1#![allow(non_snake_case)]
2use crate::AccountNumber;
3
4#[allow(dead_code)]
5type SitesContentKey = (AccountNumber, String);
6/// Decompress content
7///
8/// `DecompressorInterface` is implemented by services that can decompress content
9/// with a specific encoding.
10///
11/// Decompressor services should not inherit from this struct,
12/// instead they should define actions with matching signatures.
13///
14/// The `sites` service uses decompressor services who implement this interface to
15/// decompress content when the client's accepted encodings do not include the
16/// content's encoding.
17#[allow(dead_code)]
18struct DecompressorInterface;
19impl DecompressorInterface {
20 /// Decompresses content compressed with some algorithm
21 #[allow(dead_code, unused_variables)]
22 fn decompress(content: Vec<u8>) -> Vec<u8> {
23 unimplemented!()
24 }
25}
26
27#[crate::service(name = "sites", dispatch = false, psibase_mod = "crate")]
28#[allow(non_snake_case, unused_variables)]
29pub mod service {
30 use crate::fracpack::{Pack, ToSchema, Unpack};
31 use crate::{http::HttpRequest, AccountNumber, Checksum256, Hex};
32 use async_graphql::SimpleObject;
33 use serde::{Deserialize, Serialize};
34
35 #[table(name = "SitesContentTable", index = 0)]
36 #[derive(Debug, Clone, Serialize, Deserialize, SimpleObject, ToSchema, Pack, Unpack)]
37 #[fracpack(fracpack_mod = "fracpack")]
38 pub struct SitesContentRow {
39 pub account: AccountNumber,
40 pub path: String,
41 pub contentType: String,
42 pub contentHash: Checksum256,
43 pub contentEncoding: Option<String>,
44 pub csp: Option<String>,
45 }
46
47 impl SitesContentRow {
48 #[primary_key]
49 fn pk(&self) -> (AccountNumber, String) {
50 (self.account, self.path.clone())
51 }
52 }
53
54 #[table(name = "SiteConfigTable", index = 1)]
55 #[derive(Debug, Clone, Serialize, Deserialize, SimpleObject, ToSchema, Pack, Unpack)]
56 #[fracpack(fracpack_mod = "fracpack")]
57 pub struct SiteConfigRow {
58 #[primary_key]
59 pub account: AccountNumber,
60 pub spa: bool,
61 pub cache: bool,
62 pub globalCsp: Option<String>,
63 pub proxyAccount: Option<AccountNumber>,
64 }
65
66 #[table(name = "SitesDataTable", index = 2)]
67 #[derive(Debug, Clone, Serialize, Deserialize, ToSchema, Pack, Unpack)]
68 #[fracpack(fracpack_mod = "fracpack")]
69 pub struct SitesDataRow {
70 #[primary_key]
71 pub hash: Checksum256,
72 pub data: Vec<u8>,
73 }
74
75 #[table(name = "SitesDataRefTable", index = 3)]
76 #[derive(Debug, Clone, Serialize, Deserialize, ToSchema, Pack, Unpack)]
77 #[fracpack(fracpack_mod = "fracpack")]
78 pub struct SitesDataRefRow {
79 #[primary_key]
80 pub hash: Checksum256,
81 pub refs: u32,
82 }
83
84 /// Serves a request by looking up the content uploaded to the specified subdomain
85 #[action]
86 fn serveSys(request: HttpRequest, socket: Option<i32>) -> Option<crate::http::HttpReply> {
87 unimplemented!()
88 }
89
90 /// Stores content accessible at the caller's subdomain
91 #[action]
92 fn storeSys(
93 path: String,
94 contentType: String,
95 contentEncoding: Option<String>,
96 content: Hex<Vec<u8>>,
97 ) {
98 unimplemented!()
99 }
100
101 /// Stores content accessible at the caller's subdomain
102 #[action]
103 fn hardlink(
104 path: String,
105 contentType: String,
106 contentEncoding: Option<String>,
107 contentHash: Checksum256,
108 ) {
109 unimplemented!()
110 }
111
112 /// Removes content from the caller's subdomain
113 #[action]
114 fn remove(path: String) {
115 unimplemented!()
116 }
117
118 /// Checks whether a request for content on a site at the given path is valid (such a request will not produce a 404).
119 ///
120 /// Note: For single-page applications, static assets (e.g. 'style.css') can be checked normally. However, all other assets
121 /// are routed client-side, so a route like `/page1` is considered a valid route as long as the SPA serves a root document.
122 #[action]
123 fn isValidPath(site: AccountNumber, path: String) -> bool {
124 unimplemented!()
125 }
126
127 /// Get file properties for a given site and path
128 #[action]
129 fn getProps(site: AccountNumber, path: String) -> Option<SitesContentRow> {
130 unimplemented!()
131 }
132
133 /// Get raw file data from a site, optionally decompressing it.
134 ///
135 /// Aborts if the file is not found.
136 ///
137 /// If the file has no encoding, or decompression is not requested, the raw data is returned.
138 /// Otherwise, the data is decompressed. This action aborts if no decompressor is available
139 /// for the file's encoding.
140 #[action]
141 fn getData(site: AccountNumber, path: String, decompress: bool) -> Vec<u8> {
142 unimplemented!()
143 }
144
145 /// Enables/disables single-page application mode.
146 /// When enabled, all content requests return the root document.
147 #[action]
148 fn enableSpa(enable: bool) {
149 unimplemented!()
150 }
151
152 /// Sets the Content Security Policy for the specified path (or "*" for a global CSP).
153 /// If a specific CSP is set, it takes precedence over the global CSP.
154 /// If no specific or global CSP is set, a default CSP is used.
155 ///
156 /// The CSP string may include the keyword `{{root}}`, which is replaced
157 /// with the root domain (including port when present),
158 /// e.g. `psibase.localhost:8080` or `example.com`. Use this for
159 /// subdomain-scoped sources such as `connect-src 'self' {{root}} *.{{root}}`
160 /// so the same policy works wherever the app is served, without
161 /// hardcoding hosts.
162 #[action]
163 fn setCsp(path: String, csp: String) {
164 unimplemented!()
165 }
166
167 /// Deletes the Content Security Policy for the specified path (or "*" for the global CSP).
168 #[action]
169 fn deleteCsp(path: String) {
170 unimplemented!()
171 }
172
173 /// Enables/disables caching of responses (Enabled by default)
174 /// Cache strategy:
175 /// - `If-None-Match` header is checked against the hash of the content
176 /// - The hash is stored in the `ETag` header
177 /// - If the hash matches, a 304 Not Modified response is returned
178 /// - If the hash does not match, the new content is returned with an updated `ETag` header
179 #[action]
180 fn enableCache(enable: bool) {
181 unimplemented!()
182 }
183
184 /// When `serveSys` looks up a path on the caller's site, and no file matches (after SPA and
185 /// index rules), it tries the same path on `proxy`'s uploaded content, then on that site's
186 /// proxy if configured, and so on.
187 ///
188 /// Files that exist on the caller's site always take precedence.
189 ///
190 /// A proxy chain must not contain a cycle or `setProxy` aborts.
191 #[action]
192 fn setProxy(proxy: AccountNumber) {
193 unimplemented!()
194 }
195
196 /// Removes the proxy fallback for the caller's site. No-op if none is set.
197 #[action]
198 fn clearProxy() {
199 unimplemented!()
200 }
201}
202
203#[test]
204fn verify_schema() {
205 crate::assert_schema_matches_package::<Wrapper>();
206}