···2222# Pre-fetch modules for better caching
2323RUN go mod download
24242525-# Now copy the rest of the source
2626-COPY server/*.go ./
2727-COPY server/tap-editor ./tap-editor
2828-COPY server/templates ../server/templates
2929-COPY server/static ../server/static
2525+# Now copy the rest of the source (entire server tree)
2626+COPY server/ ./
30273131-# Bring in built web assets from the previous stage
2828+# Bring in built web assets from the previous stage (overwrites js bundle)
3229COPY --from=webbuild /app/server/static/js /app/server/static/js
33303431ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64
+1-6
README.md
···8899### Authentication
10101111-Tap uses Bluesky App Passwords (not your main account password).
1212-1313-- Enter your Bluesky handle and an App Password on the home page to sign in.
1414-- The server stores your access and refresh tokens in memory for the duration of your session.
1515-- Tokens are refreshed automatically via `com.atproto.server.refreshSession`.
1616-- You can revoke the App Password any time in your Bluesky account settings.
1111+Tap uses [Bluesky OAuth authentication](https://aaronparecki.com/2023/03/09/5/bluesky-and-oauth). Enter your Bluesky handle on the home page to sign in. You will be redirected to Bluesky to authorize the app.
17121813### Export features
1914
···11+package session
22+33+import "sync"
44+55+// Session represents a minimal legacy session persisted via cookie for
66+// non-OAuth flows and for compatibility with older endpoints.
77+type Session struct {
88+ DID string `json:"did"`
99+ Handle string `json:"handle"`
1010+ AccessJWT string `json:"accessJwt,omitempty"`
1111+ RefreshJWT string `json:"refreshJwt,omitempty"`
1212+}
1313+1414+// Store wraps the legacy session map with a mutex to provide safe concurrent
1515+// access. It retains the in-memory behaviour used previously in main.go.
1616+type Store struct {
1717+ mu sync.RWMutex
1818+ data map[string]Session
1919+}
2020+2121+// NewStore returns an initialised Store ready for use.
2222+func NewStore() *Store {
2323+ return &Store{data: make(map[string]Session)}
2424+}
2525+2626+// Get returns the session associated with id, if present.
2727+func (s *Store) Get(id string) (Session, bool) {
2828+ s.mu.RLock()
2929+ defer s.mu.RUnlock()
3030+ val, ok := s.data[id]
3131+ return val, ok
3232+}
3333+3434+// Set stores the session for the given id, replacing any previous entry.
3535+func (s *Store) Set(id string, sess Session) {
3636+ s.mu.Lock()
3737+ s.data[id] = sess
3838+ s.mu.Unlock()
3939+}
4040+4141+// Delete removes any session associated with id.
4242+func (s *Store) Delete(id string) {
4343+ s.mu.Lock()
4444+ delete(s.data, id)
4545+ s.mu.Unlock()
4646+}
4747+4848+// Keys exposes a snapshot of the current session IDs for debugging/tests.
4949+func (s *Store) Keys() []string {
5050+ s.mu.RLock()
5151+ defer s.mu.RUnlock()
5252+ keys := make([]string, 0, len(s.data))
5353+ for k := range s.data {
5454+ keys = append(keys, k)
5555+ }
5656+ return keys
5757+}
···33<head>
44 <meta charset="utf-8"/>
55 <meta name="viewport" content="width=device-width, initial-scale=1"/>
66+ <link rel="icon" type="image/png" href="/static/images/favicon.png">
67 <title>{{ .Title }}</title>
78 <link rel="stylesheet" href="/static/styles.css"/>
89 <script data-goatcounter="https://tap-editor.goatcounter.com/count"
···1314 <h1>About Tap</h1>
1415 <nav>
1516 <a href="/">Home</a>
1616- <span id="header-user" class="sp" style="margin-left:12px; opacity:.85; display:none"></span>
1717- <button id="header-logout" class="sp" style="display:none">Logout</button>
1717+ <span id="header-user" class="sp"></span>
1818+ <button id="header-logout" class="sp">Logout</button>
1819 </nav>
1920 </header>
20212122 <main class="container prose">
2223 <p>Tap is a proof-of-concept editor (in other words, a toy) for creating screenplay files in the <a href="https://fountain.io" target="_blank" rel="noreferrer">Fountain</a> format. I wrote it as an exercise to learn how <a href="https://atproto.com" target="_blank" rel="noreferrer">AT Protocol</a> works. Someday, I might add support for other Markdown-based document types.</p>
2324 <ul>
2424- <li>It does not post screenplays to your Bluesky timeline. It stores them in an AT Protocol collection (<code>lol.tapapp.tap.doc</code>) in your Bluesky profile on their personal data server (PDS). Someday, Tap will support non-Bluesky AT Protocol PDSes.</li>
2525+ <li>It does not post screenplays to your Bluesky timeline. It stores them in an AT Protocol collection (<code>lol.tapapp.tap.doc</code>) in your Bluesky profile on their personal data server (PDS). Someday, Tap might support non-Bluesky AT Protocol PDSes, it depends on how much time we have to spend on it.</li>
2526 <li><strong>It is not intended for production use.</strong> If you use Tap or store screenplays in Tap, you do so at your own risk.</li>
2627 <li>It stores data unencrypted, and the data is publicly accessible on the Internet (Bluesky doesn't support private collections).</li>
2727- <li>It uses <a href="https://docs.bsky.app/blog/oauth-atproto" target="_blank" rel="noreferrer">OAuth</a> for authentication with Bluesky.</li>
2828+ <li>It uses <a href="https://docs.bsky.app/blog/oauth-atproto" target="_blank" rel="noreferrer">OAuth</a> for authentication with Bluesky. Someday, it will support authentication with other PDSes.</li>
2829 <li>It is designed for large screens. It is not optimized for mobile.</li>
2930 <li>The editor may become sluggish if you are running a writing extension like Grammarly or ProWritingAid.</li>
3031 <li>If you want a sample Fountain script to test with, you can download one from <a href="https://fountain.io/_downloads/Big-Fish.fountain" target="_blank" rel="noreferrer">the Fountain website</a>.</li>
···33<head>
44 <meta charset="utf-8"/>
55 <meta name="viewport" content="width=device-width, initial-scale=1"/>
66+ <link rel="icon" type="image/png" href="/static/images/favicon.png">
67 <title>{{ .Title }}</title>
78 <link rel="stylesheet" href="/static/styles.css"/>
89 <script data-goatcounter="https://tap-editor.goatcounter.com/count"
···6263 <ul>
6364 <li>Access the service without providing personal information</li>
6465 <li>Use the service without creating an account</li>
6565- <li>Revoke your Bluesky App Password at any time</li>
6666+ <li>Revoke access to your Bluesky account at any time</li>
6667 <li>Clear your browser data to remove any local session information</li>
6768 </ul>
6869