Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

nightsky.gno

8.95 Kb · 300 lines
  1package nightsky
  2
  3import (
  4	"chain"
  5	"strings"
  6	"time"
  7
  8	"gno.land/p/moul/md/v0"
  9	"gno.land/p/nt/avl/v0"
 10	"gno.land/p/nt/ufmt/v0"
 11	nightsky "gno.land/p/nym-vikbez000/nightsky/v1"
 12	"gno.land/r/sys/users"
 13)
 14
 15// maxRecentCaptures is the number of captures kept in the network-wide feed.
 16const maxRecentCaptures = 50
 17
 18var (
 19	// Registry of all telescopes in the network, keyed by realm pkgPath.
 20	// An avl.Tree is used over a map: the registry grows over time, only the
 21	// search path is loaded on access, and iteration is in deterministic
 22	// (sorted) key order — important for stable Render output.
 23	telescopes = avl.NewTree()
 24
 25	// Recent captures from all telescopes (bounded, newest-first).
 26	recentCaptures = nightsky.NewCaptureFeed(maxRecentCaptures)
 27)
 28
 29// Register registers a telescope realm in the NightSky network.
 30// Call from your realm's init() function:
 31//
 32//	nightsky.Register(cross(cur), config)
 33func Register(cur realm, cfg nightsky.TelescopeConfig) {
 34	submission := cur.Previous()
 35	pkgPath := submission.PkgPath()
 36
 37	if submission.IsUser() {
 38		panic("Register must be called from realm code, not a user transaction")
 39	}
 40
 41	if telescopes.Has(pkgPath) {
 42		return // already registered, silently ignore duplicate
 43	}
 44
 45	if cfg.Name == "" || cfg.Model == "" || cfg.Owner == "" {
 46		panic("telescope name, model, and owner cannot be empty")
 47	}
 48
 49	cfg.RealmPath = pkgPath
 50
 51	telescopes.Set(pkgPath, cfg)
 52
 53	chain.Emit("TelescopeRegistered", "pkgPath", pkgPath, "name", cfg.Name, "owner", cfg.Owner)
 54}
 55
 56// UpdateTelescopeStatus allows a registered telescope realm to update its status in the registry.
 57// Call from your realm: nightsky.UpdateTelescopeStatus(cross(cur), "online")
 58func UpdateTelescopeStatus(cur realm, status string) {
 59	submission := cur.Previous()
 60	pkgPath := submission.PkgPath()
 61
 62	if submission.IsUser() {
 63		panic("must be called from realm code")
 64	}
 65
 66	config := mustGetTelescope(pkgPath)
 67	config.UpdateStatus(status)
 68	telescopes.Set(pkgPath, config)
 69
 70	chain.Emit("StatusUpdate", "pkgPath", pkgPath, "status", status)
 71}
 72
 73// SubmitCapture allows a registered telescope realm to add a capture to the network feed.
 74// Call from your realm: nightsky.SubmitCapture(cross(cur), imageURL, ra, dec, exposure, capturedBy)
 75func SubmitCapture(cur realm, imageURL string, targetRA, targetDec float64, exposure int, capturedBy string) {
 76	submission := cur.Previous()
 77	pkgPath := submission.PkgPath()
 78
 79	if submission.IsUser() {
 80		panic("must be called from realm code")
 81	}
 82
 83	config := mustGetTelescope(pkgPath)
 84
 85	if imageURL == "" {
 86		panic("image URL cannot be empty")
 87	}
 88
 89	recentCaptures.Add(nightsky.CaptureResult{
 90		ImageURL:    imageURL,
 91		TargetRA:    targetRA,
 92		TargetDec:   targetDec,
 93		Exposure:    exposure,
 94		CapturedBy:  capturedBy,
 95		CapturedAt:  time.Now(),
 96		TelescopeID: config.Name,
 97	})
 98
 99	chain.Emit("NetworkCapture", "pkgPath", pkgPath, "telescope", config.Name, "imageURL", imageURL, "capturedBy", capturedBy)
100}
101
102// UnregisterTelescope removes a telescope realm from the network.
103// Call from your realm: nightsky.UnregisterTelescope(cross(cur))
104func UnregisterTelescope(cur realm) {
105	submission := cur.Previous()
106	pkgPath := submission.PkgPath()
107
108	if submission.IsUser() {
109		panic("must be called from realm code")
110	}
111
112	if _, removed := telescopes.Remove(pkgPath); !removed {
113		panic("telescope not registered")
114	}
115
116	chain.Emit("TelescopeUnregistered", "pkgPath", pkgPath)
117}
118
119// mustGetTelescope returns the config for pkgPath or panics if not registered.
120func mustGetTelescope(pkgPath string) nightsky.TelescopeConfig {
121	if !telescopes.Has(pkgPath) {
122		panic("telescope not registered")
123	}
124	return telescopes.Get(pkgPath).(nightsky.TelescopeConfig)
125}
126
127// GetTelescopeCount returns the number of registered telescopes
128func GetTelescopeCount() int {
129	return telescopes.Size()
130}
131
132// GetOnlineTelescopeCount returns the number of online telescopes
133func GetOnlineTelescopeCount() int {
134	count := 0
135	telescopes.Iterate("", "", func(_ string, value any) bool {
136		if value.(nightsky.TelescopeConfig).Status == "online" {
137			count++
138		}
139		return false
140	})
141	return count
142}
143
144func usernameOrAddressLink(addr string) string {
145	user := users.ResolveAddress(address(addr))
146	if user != nil {
147		return user.RenderLink("")
148	}
149	return addr
150}
151
152func Render(path string) string {
153	if path == "telescopes" {
154		return renderTelescopeList()
155	}
156
157	if path == "captures" {
158		return renderRecentCaptures()
159	}
160
161	if path == "map" {
162		return renderTelescopeMap()
163	}
164
165	return renderMainPage()
166}
167
168func renderMainPage() string {
169	out := ""
170	out += md.H1("Gno NightSky 🔭")
171	out += "Decentralized telescope network for stargazing\n\n"
172	out += "Connect your smart telescope to the Gno.land blockchain and share access to the stars!\n\n"
173
174	out += md.H2("Network Status")
175	totalTelescopes := GetTelescopeCount()
176	onlineTelescopes := GetOnlineTelescopeCount()
177	out += ufmt.Sprintf("🔭 Total Telescopes: %d\n\n", totalTelescopes)
178	out += ufmt.Sprintf("🟢 Online: %d\n\n", onlineTelescopes)
179	out += ufmt.Sprintf("🔴 Offline: %d\n\n", totalTelescopes-onlineTelescopes)
180
181	out += md.H2("Quick Links")
182	out += md.Link("View All Telescopes", "/r/nym-vikbez000/nightsky/v1:telescopes") + "\n\n"
183	out += md.Link("Recent Captures", "/r/nym-vikbez000/nightsky/v1:captures") + "\n\n"
184	out += md.Link("Telescope Map", "/r/nym-vikbez000/nightsky/v1:map") + "\n\n"
185
186	if latest, ok := recentCaptures.Latest(); ok {
187		out += md.H2("Latest Capture")
188		out += "![Latest capture](" + latest.ImageURL + ")\n\n"
189		out += ufmt.Sprintf("_RA %.8fh · Dec %.8f° · %ds · %s_\n\n",
190			latest.TargetRA, latest.TargetDec, latest.Exposure, latest.TelescopeID)
191	}
192
193	out += md.HorizontalRule()
194
195	out += md.H2("How to Register Your Telescope")
196	out += "Deploy your own telescope realm using the `/p/nym-vikbez000/nightsky/v1` package,\n"
197	out += "then call `nightsky.Register(cross(cur), ...)` from your `init()` function.\n\n"
198	out += "You can clone " + md.Link("my telescope realm", "/r/nym-vikbez000/telescope") + " as a starting point.\n\n"
199
200	out += md.H2("About")
201	out += "NightSky enables telescope owners to share access to their equipment through a decentralized network.\n\n"
202	out += "Also take a look at " + md.Link("PiaGno", "https://github.com/gnoverse/piagno") + " 🎹\n\n"
203
204	return out
205}
206
207func renderTelescopeList() string {
208	out := ""
209	out += md.H1("NightSky - Telescope Network")
210
211	if telescopes.Size() == 0 {
212		out += "\nNo telescopes registered yet.\n\n"
213		out += md.Link("← Back to main page", "/r/nym-vikbez000/nightsky/v1") + "\n\n"
214		return out
215	}
216
217	out += ufmt.Sprintf("\nTotal: %d telescopes\n\n", telescopes.Size())
218
219	telescopes.Iterate("", "", func(_ string, value any) bool {
220		config := value.(nightsky.TelescopeConfig)
221		statusIcon := "🔴"
222		switch config.Status {
223		case "online":
224			statusIcon = "🟢"
225		case "busy":
226			statusIcon = "🟡"
227		case "error":
228			statusIcon = "🔴"
229		}
230
231		out += md.H3(statusIcon + " " + config.Name)
232		out += ufmt.Sprintf("**Model:** %s\n\n", config.Model)
233		out += ufmt.Sprintf("**Owner:** %s\n\n", usernameOrAddressLink(config.Owner))
234		out += ufmt.Sprintf("**Location:** %.4f°, %.4f°\n\n", config.Latitude, config.Longitude)
235		out += ufmt.Sprintf("**Status:** %s\n\n", config.Status)
236		out += ufmt.Sprintf("**Realm:** %s\n\n", md.Link("Visit", strings.TrimPrefix(config.RealmPath, "gno.land")))
237		out += md.HorizontalRule()
238		return false
239	})
240
241	out += md.Link("← Back to main page", "/r/nym-vikbez000/nightsky/v1") + "\n\n"
242
243	return out
244}
245
246func renderRecentCaptures() string {
247	out := ""
248	out += md.H1("NightSky - Recent Captures")
249
250	if recentCaptures.Len() == 0 {
251		out += "\nNo captures yet.\n\n"
252		out += md.Link("← Back to main page", "/r/nym-vikbez000/nightsky/v1") + "\n\n"
253		return out
254	}
255
256	out += ufmt.Sprintf("\nShowing %d most recent captures\n\n", recentCaptures.Len())
257
258	i := 0
259	recentCaptures.IterateNewest(func(capture nightsky.CaptureResult) bool {
260		i++
261		out += md.H3(ufmt.Sprintf("Capture #%d", i))
262		out += ufmt.Sprintf("**Telescope:** %s\n\n", capture.TelescopeID)
263		out += ufmt.Sprintf("**Captured by:** %s\n\n", usernameOrAddressLink(capture.CapturedBy))
264		out += ufmt.Sprintf("**Target:** RA %.8fh, Dec %.8f°\n\n", capture.TargetRA, capture.TargetDec)
265		out += ufmt.Sprintf("**Exposure:** %d seconds\n\n", capture.Exposure)
266		out += ufmt.Sprintf("**Image:** %s\n\n", md.Link("View", capture.ImageURL))
267		out += md.HorizontalRule()
268		return false
269	})
270
271	out += md.Link("← Back to main page", "/r/nym-vikbez000/nightsky/v1") + "\n\n"
272
273	return out
274}
275
276func renderTelescopeMap() string {
277	out := ""
278	out += md.H1("NightSky - Telescope Map")
279	out += "\n📍 Interactive map coming soon!\n\n"
280	out += "For now, here are the telescope locations:\n\n"
281
282	if telescopes.Size() == 0 {
283		out += "No telescopes registered yet.\n\n"
284	} else {
285		telescopes.Iterate("", "", func(_ string, value any) bool {
286			config := value.(nightsky.TelescopeConfig)
287			out += ufmt.Sprintf("- **%s**: %.4f°, %.4f° (%s)\n\n",
288				config.Name,
289				config.Latitude,
290				config.Longitude,
291				config.Status,
292			)
293			return false
294		})
295	}
296
297	out += md.Link("← Back to main page", "/r/nym-vikbez000/nightsky/v1") + "\n\n"
298
299	return out
300}