This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

Themes dedicated tables

author
pds.dad
date (Jul 1, 2026, 2:18 PM -0500) commit 7774fca1 parent c617b520 change-id wnwzupuw
+376 -11
+12
api/lichess/puzzledefs.go
··· 29 29 // themes: a list of puzzle themes. 30 30 Themes []string `json:"themes,omitempty" cborgen:"themes,omitempty"` 31 31 } 32 + 33 + // PuzzleDefs_ThemeView is a "themeView" in the org.lichess.puzzle.defs schema. 34 + // 35 + // A puzzle theme with its display name and description. 36 + type PuzzleDefs_ThemeView struct { 37 + // description: a sentence describing the theme. 38 + Description string `json:"description" cborgen:"description"` 39 + // key: the theme identifier used in puzzle themes and the getRandom themes filter (e.g. "fork"). 40 + Key string `json:"key" cborgen:"key"` 41 + // name: the human-readable display name of the theme (e.g. "Fork"). 42 + Name string `json:"name" cborgen:"name"` 43 + }
+27
api/lichess/puzzlegetThemes.go
··· 1 + // Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT. 2 + 3 + // Lexicon schema: org.lichess.puzzle.getThemes 4 + 5 + package lichess 6 + 7 + import ( 8 + "context" 9 + 10 + lexutil "github.com/bluesky-social/indigo/lex/util" 11 + ) 12 + 13 + // PuzzleGetThemes_Output is the output of a org.lichess.puzzle.getThemes call. 14 + type PuzzleGetThemes_Output struct { 15 + // themes: all known puzzle themes. 16 + Themes []*PuzzleDefs_ThemeView `json:"themes" cborgen:"themes"` 17 + } 18 + 19 + // PuzzleGetThemes calls the XRPC method "org.lichess.puzzle.getThemes". 20 + func PuzzleGetThemes(ctx context.Context, c lexutil.LexClient) (*PuzzleGetThemes_Output, error) { 21 + var out PuzzleGetThemes_Output 22 + if err := c.LexDo(ctx, lexutil.Query, "", "org.lichess.puzzle.getThemes", nil, nil, &out); err != nil { 23 + return nil, err 24 + } 25 + 26 + return &out, nil 27 + }
+11
cmd/puzzleimport/puzzleimport.go
··· 184 184 n, readErr := reader.Read(buf) 185 185 if n > 0 { 186 186 batch = batch[:0] 187 + themeRows := make([]models.PuzzleTheme, 0, readBatch) 187 188 for _, e := range buf[:n] { 188 189 batch = append(batch, e.toModel()) 190 + for _, t := range e.Themes { 191 + themeRows = append(themeRows, models.PuzzleTheme{Theme: t, PuzzleID: e.PuzzleID}) 192 + } 189 193 } 190 194 if err := gdb.Clauses(clause.OnConflict{UpdateAll: true}).Create(&batch).Error; err != nil { 191 195 return written, err 196 + } 197 + // Normalized (theme, puzzle) rows for indexed theme filtering. DoNothing 198 + // because the composite PK makes re-imported rows collide harmlessly. 199 + if len(themeRows) > 0 { 200 + if err := gdb.Clauses(clause.OnConflict{DoNothing: true}).Create(&themeRows).Error; err != nil { 201 + return written, err 202 + } 192 203 } 193 204 written += int64(n) 194 205 }
+71 -1
internal/db/db.go
··· 3 3 package db 4 4 5 5 import ( 6 + _ "embed" 7 + "encoding/xml" 6 8 "fmt" 9 + "strings" 7 10 8 11 "gorm.io/driver/sqlite" 9 12 "gorm.io/gorm" 13 + "gorm.io/gorm/clause" 10 14 "gorm.io/gorm/logger" 11 15 "tangled.org/pds.dad/PuzzleMeThis/internal/models" 12 16 ) 17 + 18 + // themesXML is the Lichess strings resource file bundled at build time. It maps 19 + // each theme key to a display name (e.g. "fork" -> "Fork") and a description 20 + // (via the "<key>Description" entry), and is the source for SeedThemes. 21 + // 22 + //go:embed themes.xml 23 + var themesXML []byte 13 24 14 25 // Open opens the SQLite database at path with settings tuned for bulk inserts 15 26 // and applies performance PRAGMAs. SQLite is a single writer, so the connection ··· 57 68 } 58 69 59 70 func Migrate(gdb *gorm.DB) error { 60 - if err := gdb.AutoMigrate(&models.Puzzle{}); err != nil { 71 + if err := gdb.AutoMigrate(&models.Puzzle{}, &models.PuzzleTheme{}, &models.Theme{}); err != nil { 61 72 return fmt.Errorf("auto-migrate: %w", err) 62 73 } 74 + if err := SeedThemes(gdb); err != nil { 75 + return fmt.Errorf("seed themes: %w", err) 76 + } 63 77 return nil 64 78 } 79 + 80 + // SeedThemes populates the themes reference table from the bundled themes.xml. 81 + // It upserts, so it is idempotent and safe to run on every startup. 82 + func SeedThemes(gdb *gorm.DB) error { 83 + themes, err := parseThemes(themesXML) 84 + if err != nil { 85 + return err 86 + } 87 + if len(themes) == 0 { 88 + return nil 89 + } 90 + if err := gdb.Clauses(clause.OnConflict{UpdateAll: true}).Create(&themes).Error; err != nil { 91 + return err 92 + } 93 + return nil 94 + } 95 + 96 + // parseThemes turns the Lichess strings XML into Theme rows. Each theme key has 97 + // a "<key>" name entry (display name) and a "<key>Description" entry. Keys that 98 + // only carry a description (e.g. enPassant) fall back to the key as the name. 99 + // The non-theme "puzzleDownloadInformation" entry is skipped. 100 + func parseThemes(data []byte) ([]models.Theme, error) { 101 + var doc struct { 102 + Strings []struct { 103 + Name string `xml:"name,attr"` 104 + Value string `xml:",chardata"` 105 + } `xml:"string"` 106 + } 107 + if err := xml.Unmarshal(data, &doc); err != nil { 108 + return nil, err 109 + } 110 + 111 + values := make(map[string]string, len(doc.Strings)) 112 + for _, s := range doc.Strings { 113 + values[s.Name] = strings.TrimSpace(s.Value) 114 + } 115 + 116 + const descSuffix = "Description" 117 + themes := make([]models.Theme, 0, len(values)) 118 + for name, desc := range values { 119 + if !strings.HasSuffix(name, descSuffix) { 120 + continue 121 + } 122 + key := strings.TrimSuffix(name, descSuffix) 123 + if key == "" || key == "puzzleDownloadInformation" { 124 + continue 125 + } 126 + display := values[key] 127 + if display == "" { 128 + // Description-only key (e.g. enPassant): use the key itself. 129 + display = key 130 + } 131 + themes = append(themes, models.Theme{Key: key, Name: display, Description: desc}) 132 + } 133 + return themes, nil 134 + }
+153
internal/db/themes.xml
··· 1 + <?xml version="1.0" encoding="UTF-8"?> 2 + <resources> 3 + <string name="advancedPawn">Advanced pawn</string> 4 + <string name="advancedPawnDescription">One of your pawns is deep into the opponent position, maybe threatening to promote.</string> 5 + <string name="advantage">Advantage</string> 6 + <string name="advantageDescription">Seize your chance to get a decisive advantage. (200cp ≤ eval ≤ 600cp)</string> 7 + <string name="anastasiaMate">Anastasia's mate</string> 8 + <string name="anastasiaMateDescription">A knight and rook or queen team up to trap the opposing king between the side of the board and a friendly piece.</string> 9 + <string name="arabianMate">Arabian mate</string> 10 + <string name="arabianMateDescription">A knight and a rook team up to trap the opposing king on a corner of the board.</string> 11 + <string name="attackingF2F7">Attacking f2 or f7</string> 12 + <string name="attackingF2F7Description">An attack focusing on the f2 or f7 pawn, such as in the fried liver opening.</string> 13 + <string name="attraction">Attraction</string> 14 + <string name="attractionDescription">An exchange or sacrifice encouraging or forcing an opponent piece to a square that allows a follow-up tactic.</string> 15 + <string name="backRankMate">Back rank mate</string> 16 + <string name="backRankMateDescription">Checkmate the king on the home rank, when it is trapped there by its own pieces.</string> 17 + <string name="balestraMate">Balestra mate</string> 18 + <string name="balestraMateDescription">A bishop delivers the checkmate, while a queen blocks the remaining escape squares</string> 19 + <string name="blindSwineMate">Blind Swine mate</string> 20 + <string name="blindSwineMateDescription">Two rooks team up to mate the king in an area of 2 by 2 squares.</string> 21 + <string name="bishopEndgame">Bishop endgame</string> 22 + <string name="bishopEndgameDescription">An endgame with only bishops and pawns.</string> 23 + <string name="bodenMate">Boden's mate</string> 24 + <string name="bodenMateDescription">Two attacking bishops on criss-crossing diagonals deliver mate to a king obstructed by friendly pieces.</string> 25 + <string name="castling">Castling</string> 26 + <string name="castlingDescription">Bring the king to safety, and deploy the rook for attack.</string> 27 + <string name="capturingDefender">Capture the defender</string> 28 + <string name="capturingDefenderDescription">Removing a piece that is critical to defence of another piece, allowing the now undefended piece to be captured on a following move.</string> 29 + <string name="collinearMove">Collinear move</string> 30 + <string name="collinearMoveDescription">Two opposing pieces face each other, and one slides along the line of attack without capturing the enemy piece.</string> 31 + <string name="cornerMate">Corner mate</string> 32 + <string name="cornerMateDescription">Confine the king to the corner using a rook or queen and a knight to engage the checkmate.</string> 33 + <string name="crushing">Crushing</string> 34 + <string name="crushingDescription">Spot the opponent blunder to obtain a crushing advantage. (eval ≥ 600cp)</string> 35 + <string name="discoveredCheck">Discovered check</string> 36 + <string name="discoveredCheckDescription">Move a piece to reveal a check from a hidden attacking piece, which often leads to a decisive advantage.</string> 37 + <string name="doubleBishopMate">Double bishop mate</string> 38 + <string name="doubleBishopMateDescription">Two attacking bishops on adjacent diagonals deliver mate to a king obstructed by friendly pieces.</string> 39 + <string name="dovetailMate">Dovetail mate</string> 40 + <string name="dovetailMateDescription">A queen delivers mate to an adjacent king, whose only two escape squares are obstructed by friendly pieces.</string> 41 + <string name="equality">Equality</string> 42 + <string name="equalityDescription">Come back from a losing position, and secure a draw or a balanced position. (eval ≤ 200cp)</string> 43 + <string name="kingsideAttack">Kingside attack</string> 44 + <string name="kingsideAttackDescription">An attack of the opponent's king, after they castled on the king side.</string> 45 + <string name="clearance">Clearance</string> 46 + <string name="clearanceDescription">A move, often with tempo, that clears a square, file or diagonal for a follow-up tactical idea.</string> 47 + <string name="defensiveMove">Defensive move</string> 48 + <string name="defensiveMoveDescription">A precise move or sequence of moves that is needed to avoid losing material or another advantage.</string> 49 + <string name="deflection">Deflection</string> 50 + <string name="deflectionDescription">A move that distracts an opposing piece from another duty that it performs, such as guarding a key square. Sometimes also called "overloading".</string> 51 + <string name="discoveredAttack">Discovered attack</string> 52 + <string name="discoveredAttackDescription">Moving a piece (such as a knight), that previously blocked an attack by a long range piece (such as a rook), out of the way of that piece.</string> 53 + <string name="doubleCheck">Double check</string> 54 + <string name="doubleCheckDescription">Checking with two pieces at once, as a result of a discovered attack where both the moving piece and the unveiled piece attack the opponent's king.</string> 55 + <string name="endgame">Endgame</string> 56 + <string name="endgameDescription">A tactic during the last phase of the game.</string> 57 + <string name="enPassantDescription">A tactic involving the en passant rule, where a pawn can capture an opponent pawn that has bypassed it using its initial two-square move.</string> 58 + <string name="epauletteMate">Epaulette mate</string> 59 + <string name="epauletteMateDescription">Two adjacent escape squares for a checked king are occupied by other pieces.</string> 60 + <string name="exposedKing">Exposed king</string> 61 + <string name="exposedKingDescription">A tactic involving a king with few defenders around it, often leading to checkmate.</string> 62 + <string name="fork">Fork</string> 63 + <string name="forkDescription">A move where the moved piece attacks two opponent pieces at once.</string> 64 + <string name="hangingPiece">Hanging piece</string> 65 + <string name="hangingPieceDescription">A tactic involving an opponent piece being undefended or insufficiently defended and free to capture.</string> 66 + <string name="hookMate">Hook mate</string> 67 + <string name="hookMateDescription">Checkmate with a rook, knight, and pawn along with one enemy pawn to limit the enemy king's escape.</string> 68 + <string name="interference">Interference</string> 69 + <string name="interferenceDescription">Moving a piece between two opponent pieces to leave one or both opponent pieces undefended, such as a knight on a defended square between two rooks.</string> 70 + <string name="intermezzo">Intermezzo</string> 71 + <string name="intermezzoDescription">Instead of playing the expected move, first interpose another move posing an immediate threat that the opponent must answer. Also known as "Zwischenzug" or "In between".</string> 72 + <string name="killBoxMate">Kill box mate</string> 73 + <string name="killBoxMateDescription">A rook is next to the enemy king and supported by a queen that also blocks the king's escape squares. The rook and the queen catch the enemy king in a 3 by 3 "kill box".</string> 74 + <string name="pillsburysMate">Pillsbury's mate</string> 75 + <string name="pillsburysMateDescription">The rook delivers checkmate, while the bishop helps to confine it.</string> 76 + <string name="morphysMate">Morphy's mate</string> 77 + <string name="morphysMateDescription">Use the bishop to check the king, while your rook helps to confine it.</string> 78 + <string name="swallowstailMate">Swallow's tail mate</string> 79 + <string name="swallowstailMateDescription">A checkmate pattern that visually resembles the appearance of a swallow’s tail, similar to a V shape.</string> 80 + <string name="triangleMate">Triangle mate</string> 81 + <string name="triangleMateDescription">The queen and rook, one square away from the enemy king, are on the same rank or file, separated by one square, forming a triangle.</string> 82 + <string name="vukovicMate">Vuković mate</string> 83 + <string name="vukovicMateDescription">A rook and knight team up to mate the king. The rook delivers mate while supported by a third piece, and the knight is used to block the king's escape squares.</string> 84 + <string name="knightEndgame">Knight endgame</string> 85 + <string name="knightEndgameDescription">An endgame with only knights and pawns.</string> 86 + <string name="long">Long puzzle</string> 87 + <string name="longDescription">Three moves to win.</string> 88 + <string name="master">Master games</string> 89 + <string name="masterDescription">Puzzles from games played by titled players.</string> 90 + <string name="masterVsMaster">Master vs Master games</string> 91 + <string name="masterVsMasterDescription">Puzzles from games between two titled players.</string> 92 + <string name="mate">Checkmate</string> 93 + <string name="mateDescription">Win the game with style.</string> 94 + <string name="mateIn1">Mate in 1</string> 95 + <string name="mateIn1Description">Deliver checkmate in one move.</string> 96 + <string name="mateIn2">Mate in 2</string> 97 + <string name="mateIn2Description">Deliver checkmate in two moves.</string> 98 + <string name="mateIn3">Mate in 3</string> 99 + <string name="mateIn3Description">Deliver checkmate in three moves.</string> 100 + <string name="mateIn4">Mate in 4</string> 101 + <string name="mateIn4Description">Deliver checkmate in four moves.</string> 102 + <string name="mateIn5">Mate in 5 or more</string> 103 + <string name="mateIn5Description">Figure out a long mating sequence.</string> 104 + <string name="middlegame">Middlegame</string> 105 + <string name="middlegameDescription">A tactic during the second phase of the game.</string> 106 + <string name="oneMove">One-move puzzle</string> 107 + <string name="oneMoveDescription">A puzzle that is only one move long.</string> 108 + <string name="opening">Opening</string> 109 + <string name="openingDescription">A tactic during the first phase of the game.</string> 110 + <string name="operaMate">Opera mate</string> 111 + <string name="operaMateDescription">Check the king with a rook and use a bishop to defend the rook.</string> 112 + <string name="pawnEndgame">Pawn endgame</string> 113 + <string name="pawnEndgameDescription">An endgame with only pawns.</string> 114 + <string name="pin">Pin</string> 115 + <string name="pinDescription">A tactic involving pins, where a piece is unable to move without revealing an attack on a higher value piece.</string> 116 + <string name="promotion">Promotion</string> 117 + <string name="promotionDescription">Promote one of your pawn to a queen or minor piece.</string> 118 + <string name="queenEndgame">Queen endgame</string> 119 + <string name="queenEndgameDescription">An endgame with only queens and pawns.</string> 120 + <string name="queenRookEndgame">Queen and Rook</string> 121 + <string name="queenRookEndgameDescription">An endgame with only queens, rooks and pawns.</string> 122 + <string name="queensideAttack">Queenside attack</string> 123 + <string name="queensideAttackDescription">An attack of the opponent's king, after they castled on the queen side.</string> 124 + <string name="quietMove">Quiet move</string> 125 + <string name="quietMoveDescription">A move that does not check, capture, or create an immediate threat to capture. Instead, it prepares a hidden and unavoidable threat for a later move.</string> 126 + <string name="rookEndgame">Rook endgame</string> 127 + <string name="rookEndgameDescription">An endgame with only rooks and pawns.</string> 128 + <string name="sacrifice">Sacrifice</string> 129 + <string name="sacrificeDescription">A tactic involving giving up material in the short-term, to gain an advantage again after a forced sequence of moves.</string> 130 + <string name="short">Short puzzle</string> 131 + <string name="shortDescription">Two moves to win.</string> 132 + <string name="skewer">Skewer</string> 133 + <string name="skewerDescription">A motif involving a high value piece being attacked, moving out the way, and allowing a lower value piece behind it to be captured or attacked, the inverse of a pin.</string> 134 + <string name="smotheredMate">Smothered mate</string> 135 + <string name="smotheredMateDescription">A checkmate delivered by a knight in which the mated king is unable to move because it is surrounded (or smothered) by its own pieces.</string> 136 + <string name="superGM">Super GM games</string> 137 + <string name="superGMDescription">Puzzles from games played by the best players in the world.</string> 138 + <string name="trappedPiece">Trapped piece</string> 139 + <string name="trappedPieceDescription">A piece is unable to escape capture as it has limited moves.</string> 140 + <string name="underPromotion">Underpromotion</string> 141 + <string name="underPromotionDescription">Promotion to a knight, bishop, or rook.</string> 142 + <string name="veryLong">Very long puzzle</string> 143 + <string name="veryLongDescription">Four moves or more to win.</string> 144 + <string name="xRayAttack">X-Ray attack</string> 145 + <string name="xRayAttackDescription">A piece attacks or defends a square, through an enemy piece.</string> 146 + <string name="zugzwang">Zugzwang</string> 147 + <string name="zugzwangDescription">The opponent is limited in the moves they can make, and all moves worsen their position.</string> 148 + <string name="mix">Healthy mix</string> 149 + <string name="mixDescription">A bit of everything. You don't know what to expect, so be ready for anything! Just like in real games.</string> 150 + <string name="playerGames">Player games</string> 151 + <string name="playerGamesDescription">View puzzles generated from your games, or from another player's games</string> 152 + <string name="puzzleDownloadInformation">These puzzles are in the public domain, and can be downloaded from %s.</string> 153 + </resources>
+27
internal/models/puzzle.go
··· 24 24 func (Puzzle) TableName() string { 25 25 return "puzzles" 26 26 } 27 + 28 + // PuzzleTheme is a normalized (theme, puzzle) join row used for indexed theme 29 + // filtering, avoiding a LIKE scan over the puzzles' JSON themes column. The 30 + // composite primary key is ordered (theme, puzzle_id) so it doubles as the 31 + // theme-lookup index and gives uniqueness for OnConflict on re-import. 32 + type PuzzleTheme struct { 33 + Theme string `gorm:"column:theme;primaryKey"` 34 + PuzzleID string `gorm:"column:puzzle_id;primaryKey"` 35 + } 36 + 37 + // TableName overrides the default GORM pluralization. 38 + func (PuzzleTheme) TableName() string { 39 + return "puzzle_themes" 40 + } 41 + 42 + // Theme is the reference mapping of a Lichess theme key to its display name and 43 + // description, seeded from the bundled strings XML. 44 + type Theme struct { 45 + Key string `gorm:"column:key;primaryKey"` 46 + Name string `gorm:"column:name"` 47 + Description string `gorm:"column:description"` 48 + } 49 + 50 + // TableName overrides the default GORM pluralization. 51 + func (Theme) TableName() string { 52 + return "themes" 53 + }
+28 -10
internal/server/puzzleHandlers.go
··· 4 4 "errors" 5 5 "net/http" 6 6 "strconv" 7 - "strings" 8 7 9 8 "github.com/labstack/echo/v5" 10 9 "gorm.io/gorm" ··· 61 60 return c.JSON(http.StatusOK, toPuzzleView(p)) 62 61 } 63 62 63 + // getThemes implements the org.lichess.puzzle.getThemes XRPC query: it returns 64 + // every puzzle theme with its display name and description from the themes 65 + // reference table. The keys are the values accepted by getRandom's themes filter. 66 + func (s *Server) getThemes(c *echo.Context) error { 67 + var themes []models.Theme 68 + if err := s.db.Order("key").Find(&themes).Error; err != nil { 69 + c.Logger().Error("getThemes db lookup failed", "err", err) 70 + return xrpcError(c, http.StatusInternalServerError, "InternalServerError", "failed to load themes") 71 + } 72 + 73 + views := make([]*lichess.PuzzleDefs_ThemeView, 0, len(themes)) 74 + for _, t := range themes { 75 + views = append(views, &lichess.PuzzleDefs_ThemeView{ 76 + Key: t.Key, 77 + Name: t.Name, 78 + Description: t.Description, 79 + }) 80 + } 81 + 82 + return c.JSON(http.StatusOK, &lichess.PuzzleGetThemes_Output{Themes: views}) 83 + } 84 + 64 85 // getRandomPuzzle implements the org.lichess.getRandomPuzzle XRPC query. It returns a 65 86 // single random puzzle, optionally filtered by a target rating (within ±ratingBand) and 66 87 // by themes (matching at least one). With no parameters it returns a truly random puzzle. ··· 80 101 } 81 102 82 103 // themes: optional, repeatable (?themes=a&themes=b); match puzzles containing at 83 - // least one. Themes are stored as a JSON-array text column, so each token is quoted 84 - // to avoid substring false-positives (e.g. "mate" inside "mateIn2"). 104 + // least one. Filtering uses the normalized puzzle_themes join table via an EXISTS 105 + // probe, an index-only lookup on its (theme, puzzle_id) primary key. 85 106 if themes := c.QueryParams()["themes"]; len(themes) > 0 { 86 - clauses := make([]string, 0, len(themes)) 87 - args := make([]any, 0, len(themes)) 88 - for _, t := range themes { 89 - clauses = append(clauses, "themes LIKE ?") 90 - args = append(args, "%\""+t+"\"%") 91 - } 92 - q = q.Where("("+strings.Join(clauses, " OR ")+")", args...) 107 + q = q.Where( 108 + "EXISTS (SELECT 1 FROM puzzle_themes pt WHERE pt.puzzle_id = puzzles.puzzle_id AND pt.theme IN ?)", 109 + themes, 110 + ) 93 111 filtered = true 94 112 } 95 113
+1
internal/server/server.go
··· 33 33 xrpc := e.Group("/xrpc") 34 34 xrpc.GET("/org.lichess.puzzle.getById", srv.getPuzzle) 35 35 xrpc.GET("/org.lichess.puzzle.getRandom", srv.getRandomPuzzle) 36 + xrpc.GET("/org.lichess.puzzle.getThemes", srv.getThemes) 36 37 37 38 port := viper.Get("server.port") 38 39 host := viper.Get("server.host")
+19
lexicons/org/lichess/puzzle/defs.json
··· 2 2 "lexicon": 1, 3 3 "id": "org.lichess.puzzle.defs", 4 4 "defs": { 5 + "themeView": { 6 + "type": "object", 7 + "description": "A puzzle theme with its display name and description.", 8 + "required": ["key", "name", "description"], 9 + "properties": { 10 + "key": { 11 + "type": "string", 12 + "description": "the theme identifier used in puzzle themes and the getRandom themes filter (e.g. \"fork\")." 13 + }, 14 + "name": { 15 + "type": "string", 16 + "description": "the human-readable display name of the theme (e.g. \"Fork\")." 17 + }, 18 + "description": { 19 + "type": "string", 20 + "description": "a sentence describing the theme." 21 + } 22 + } 23 + }, 5 24 "puzzleView": { 6 25 "type": "object", 7 26 "description": "The shared representation of a chess puzzle returned by the puzzle queries.",
+27
lexicons/org/lichess/puzzle/getThemes.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "org.lichess.puzzle.getThemes", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "List all puzzle themes with their display names and descriptions. The keys returned here are the values accepted by the getRandom themes filter.", 8 + "output": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["themes"], 13 + "properties": { 14 + "themes": { 15 + "type": "array", 16 + "description": "all known puzzle themes.", 17 + "items": { 18 + "type": "ref", 19 + "ref": "org.lichess.puzzle.defs#themeView" 20 + } 21 + } 22 + } 23 + } 24 + } 25 + } 26 + } 27 + }