diff --git a/.gitignore b/.gitignore index 9ff0b3d..5923e07 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,6 @@ dcider.db # Other editors . history + +# Python +__pycache__ diff --git a/schema/__init__.py b/schema/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/schema/copy_to_json.py b/schema/copy_to_json.py new file mode 100644 index 0000000..d8860ac --- /dev/null +++ b/schema/copy_to_json.py @@ -0,0 +1,16 @@ +import json +import os + +# Get the absolute path of the script's directory +script_dir = os.path.dirname(os.path.abspath(__file__)) + +# Load the GraphQL schema from file +with open(os.path.join(script_dir, "schema-generated.graphql"), "r") as graphql_file: + graphql_schema = graphql_file.read() + +# Encode the GraphQL schema as JSON +graphql_json = json.dumps(graphql_schema) + +# Save the JSON-encoded schema to a new file +with open(os.path.join(script_dir, "schema-generated.graphql.json"), "w") as json_file: + _ = json_file.write(graphql_json) diff --git a/schema/generate_lexicon_schema.py b/schema/generate_lexicon_schema.py new file mode 100644 index 0000000..19f8faa --- /dev/null +++ b/schema/generate_lexicon_schema.py @@ -0,0 +1,466 @@ +"""Convert Lexicon definitions to GraphQL""" + +import os +import re +import sys +import traceback +from pathlib import Path +from typing import Annotated, TypeAlias + +from atproto_lexicon.models import ( + LexArray, + LexBlob, + LexDefinition, + LexObject, + LexPrimitive, + LexRef, + LexRefUnion, + LexRefVariant, + LexString, + LexXrpcQuery, +) +from atproto_lexicon.parser import lexicon_parse_file +from typer import Argument, Option, run + + +def normalize_type_name(s: str) -> str: + """ + Normalize type name to PascalCase + """ + # Convert to PascalCase + return s[0].upper() + s[1:] if s else s + + +class LexiconPath: + _segments: list[str] + + def __init__(self, path: str | None = None) -> None: + if path is None: + self._segments = [] + else: + self._segments = path.split(".") + + @property + def segments(self): + return self._segments[:] + + def dot_path(self): + return ".".join(self._segments) + + def file_path(self): + return "/".join(self._segments) + + +# Constants +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +LEXICON_DIR = os.path.join(BASE_DIR, "../deps/atproto/lexicons") + +TYPE_MAPPING = { + "string": "String", + "integer": "Int", + "number": "Float", + "boolean": "Boolean", + "bytes": "String", +} + +FORMAT_MAPPING = { + "did": "ID", + "handle": "ID", + "uri": "String", + "cid": "String", + "datetime": "String", + "date": "String", + "time": "String", + "at-identifier": "ID", +} + +SKIP_FIELDS = {"debug"} # Fields to skip in GraphQL output + + +def resolve_lexicon_path(lexicon_path: LexiconPath) -> str: + """ + Resolve a lexicon ID to its file path + """ + dir_parts = lexicon_path.segments + + # Construct path: app.bsky.actor.getProfile -> app/bsky/actor/getProfile.json + file_path = os.path.join(LEXICON_DIR, *dir_parts[:-1], f"{dir_parts[-1]}.json") + + if not os.path.exists(file_path): + raise FileNotFoundError(f"Lexicon file not found: {file_path}") + + return file_path + + +def normalize_reference(base: str, ref: str) -> str: + """ + Normalize a reference like '#profileViewDetailed' + """ + if "#" in base and ref[0] == "#": + return base.split("#")[0] + ref + elif "#" not in base and ref[0] == "#": + return base + ref + else: + return ref + + +def resolve_reference(ref: str) -> LexDefinition | None: + if "#" in ref: + lexicon_id, type_name = ref.split("#", 1) + else: + lexicon_id = ref + type_name = "main" + try: + lexicon_path = resolve_lexicon_path(LexiconPath(lexicon_id)) + + # Use structured parsing instead of JSON + lexicon_structured_data = lexicon_parse_file(lexicon_path) + + # Find the type definition in the structured data + if not lexicon_structured_data: + return None + + defs = lexicon_structured_data.defs + + # Check if the type_name is in defs directly (as dict key) + if type_name in defs: + return defs[type_name] + + return None + except Exception as e: + print(f"Warning: Could not resolve reference {ref}: {e}", file=sys.stderr) + return None + + +def lex_type_to_graphql_type( + base: str, + lex_type: LexRefVariant | LexPrimitive | LexBlob | LexObject | LexArray, + is_required: bool = False, +) -> str: + """ + Convert Lexicon type to GraphQL type + """ + if isinstance(lex_type, LexArray): + items_type = lex_type.items + item_type = lex_type_to_graphql_type(base, items_type, True) + output = f"[{item_type}]" + elif isinstance(lex_type, LexRef): + # For reference types, we use the type name directly + ref = normalize_reference(base, lex_type.ref) or "" + # Convert to GraphQL naming convention - camelCase to PascalCase + type_name = f"Lexicon_{ref.replace('#', '.').replace('.', '_')}" + output = type_name + elif isinstance(lex_type, LexString): + if lex_type.format in FORMAT_MAPPING: + output = FORMAT_MAPPING[lex_type.format] + else: + output = "String" + elif lex_type.type in TYPE_MAPPING: + output = TYPE_MAPPING[lex_type.type] + else: + output = "Unknown" + + nullable_indicator = "!" if is_required else "" + return f"{output}{nullable_indicator}" + + +TypeDef: TypeAlias = LexObject | LexString +TypeDefs: TypeAlias = dict[str, TypeDef] + + +def to_upper_snake_case(s: str) -> str: + return re.sub(r"[^a-zA-Z0-9]+", "_", s).upper() + + +def lex_object_definition_to_graphql(type_name: str, type_def: TypeDef) -> str: + """ + Convert a LexObject definition from lexicon to GraphQL + """ + + if isinstance(type_def, LexString): + # Enums + graphql_lines = [ + f"# {type_name}\nenum Lexicon_{type_name.replace('#', '_').replace('.', '_')} {{" + ] + + for field_name in type_def.known_values or []: + graphql_lines.append(f" {to_upper_snake_case(field_name)}") + else: + # Struct/object definitions + graphql_lines = [ + f"# {type_name}\ntype Lexicon_{type_name.replace('#', '_').replace('.', '_')} {{" + ] + + properties = type_def.properties + required_fields = set(type_def.required or []) + + for field_name, field_def in properties.items(): + # Skip debug field as per requirements + if field_name in SKIP_FIELDS: + continue + + # Skip ref unions(?) + if isinstance(field_def, LexRefUnion): + continue + + # Skip "unknown" fields + if field_def.type == "unknown": + continue + + is_required = field_name in required_fields + graphql_type = lex_type_to_graphql_type(type_name, field_def, is_required) + graphql_lines.append(f" {field_name}: {graphql_type}") + + graphql_lines.append("}") + return "\n".join(graphql_lines) + + +# Build the nested types from the outside in +# For app.bsky.actor.getProfile, we need: +# 1. type LexiconApp { bsky: LexiconAppBsky! } +# 2. type LexiconAppBsky { actor: LexiconAppBskyActor! } +# 3. type LexiconAppBskyActor { getProfile(...): ... } +def output_lexicon_namespaces( + namespaces: dict[str, str], root: LexiconPath +) -> list[str]: + lines: list[str] = [] + chunks: list[str] = [] + root_len = len(root.segments) + while len(namespaces): + lexicon_path = LexiconPath(next(iter(namespaces))) + if root_len == len(lexicon_path.segments) - 1: + # Done, output the type + lines.append(namespaces.pop(lexicon_path.dot_path())) + else: + next_segment = lexicon_path.segments[root_len] + next_path = ( + f"{root.dot_path()}.{next_segment}" if root_len > 0 else next_segment + ) + + group: dict[str, str] = dict() + for path in list(namespaces.keys()): + if path.startswith(f"{next_path}."): + group[path] = namespaces.pop(path) + chunks += output_lexicon_namespaces(group, LexiconPath(next_path)) + + lines.append( + f"{next_segment}: {'_'.join(['Lexicon'] + LexiconPath(next_path).segments)}!" + ) + + content = " " + "\n ".join(lines) + chunks.append(f"type {'_'.join(['Lexicon'] + root.segments)} {{\n{content}\n}}\n") + return chunks + + +def generate_lexicon_structure( + lexicon_path: LexiconPath, +) -> tuple[str, TypeDefs]: + """ + Generate the nested Lexicon structure for a lexicon endpoint + Returns (graphql_type, set(refs)) + """ + + method_name = lexicon_path.segments[-1] + + try: + # Structured parsing of the Lexicon .json file + lexicon_structured_data = lexicon_parse_file(resolve_lexicon_path(lexicon_path)) + + if not lexicon_structured_data: + return ("", dict()) + + defs = lexicon_structured_data.defs + main_def = defs["main"] + assert isinstance(main_def, LexXrpcQuery) + + # Build the method definition + referenced_types: set[str] = set() + object_definitions: TypeDefs = dict() + + # Add parameters as fields + method_fields: list[str] = [] + if main_def.parameters: + params = main_def.parameters + required_fields = params.required or [] + for field_name, field_def in params.properties.items(): + # Convert the structured field definition to dict for get_graphql_type + is_required = field_name in required_fields + graphql_type = lex_type_to_graphql_type( + lexicon_path.dot_path(), field_def, is_required + ) + method_fields.append(f"{field_name}: {graphql_type}") + + # Sweep up refs + if isinstance(field_def, LexRef): + referenced_types.add(field_def.ref) + + method_params = f"({', '.join(method_fields)})" if len(method_fields) else "" + + return_type = None + + if main_def.output: + output_schema = main_def.output.schema_ + if isinstance(output_schema, LexRef): + # Use the output type as return type + return_type = lex_type_to_graphql_type( + lexicon_path.dot_path(), output_schema + ) + + # Sweep up refs + referenced_types.add(output_schema.ref) + elif isinstance(output_schema, LexObject): + # Handle inline object definitions for return type + output_type = f"Lexicon_{'_'.join(lexicon_path.segments)}Output" + return_type = output_type + object_definitions[lexicon_path.dot_path() + "Output"] = output_schema + + # Resolve references + for ref in referenced_types: + ref = normalize_reference(lexicon_path.dot_path(), ref) + ref_def = resolve_reference(ref) + if isinstance(ref_def, TypeDef): + object_definitions[ref] = ref_def + + if return_type: + method_def = f"{method_name}{method_params}: {return_type}" + else: + method_def = f"{method_name}{method_params}" + + return (method_def, object_definitions) + except Exception as e: + print( + f"Error processing lexicon {'.'.join(lexicon_path.segments)}: {e}", + file=sys.stderr, + ) + traceback.print_exc() + return ("", dict()) + + +def generate_definitions(lexicon_ids: list[str]) -> list[str]: + chunks: list[str] = [] + lexicon_fields: dict[str, str] = dict() + object_definitions: TypeDefs = dict() + for lexicon_id in lexicon_ids: + lexicon_path = LexiconPath(lexicon_id) + + # Generate main lexicon structure + method_type, collected_object_definitions = generate_lexicon_structure( + lexicon_path + ) + lexicon_fields[lexicon_path.dot_path()] = method_type + object_definitions.update(collected_object_definitions) + + # Walk output types for more refs + final_definitions: TypeDefs = dict() + new_definitions: TypeDefs = dict() + while len(object_definitions): + for obj_name, obj_def in object_definitions.items(): + if isinstance(obj_def, LexObject): + for field_def in obj_def.properties.values(): + # Export refs + if isinstance(field_def, LexRef): + ref = normalize_reference(obj_name, field_def.ref) + ref_def = resolve_reference(ref) + if isinstance(ref_def, TypeDef): + new_definitions[ref] = ref_def + continue + elif isinstance(field_def, LexArray) and isinstance( + field_def.items, LexRef + ): + ref = normalize_reference(obj_name, field_def.items.ref) + ref_def = resolve_reference(ref) + if isinstance(ref_def, LexObject) or isinstance( + ref_def, LexString + ): + new_definitions[ref] = ref_def + continue + + final_definitions[obj_name] = obj_def + + # Reset object_definitions to only be ones we haven't seen yet. + object_definitions = dict() + for obj_key, obj_def in new_definitions.items(): + if obj_key not in final_definitions: + object_definitions[obj_key] = obj_def + object_definitions = final_definitions + + # Generate types for all output types + for name, type in object_definitions.items(): + chunks.append(lex_object_definition_to_graphql(name, type) + "\n\n") + + # Generate Lexicon structs iteratively. + chunks += output_lexicon_namespaces(lexicon_fields, LexiconPath()) + + return chunks + + +def read_schema_files(schema_files: list[str] | None) -> str: + """Read content from schema files or folders recursively.""" + if not schema_files: + return "" + + output = "" + for schema_file in schema_files: + path = Path(schema_file) + if path.is_file(): + # If it's a single file, read it + with open(str(path), "r") as f: + content = f.read() + output += "\n" + content + "\n" + elif path.is_dir(): + # If it's a directory, recursively read all .graphql files + for graphql_file in path.rglob("*.graphql"): + with open(str(graphql_file), "r") as f: + content = f.read() + output += "\n" + content + "\n" + return output + + +def main( + lexicon_ids: Annotated[ + list[str], + Argument( + ..., + help="List of lexicon identifiers to generate definitions for", + ), + ], + append_schema: Annotated[ + list[str], + Option( + "--append-schema", + "-a", + help="Additional GraphQL schema files or folders to append. Can be used multiple times.", + ), + ], + output: Annotated[ + str, + Option( + "--output", + "-o", + help="Output file path (default: stdout)", + ), + ], +) -> None: + """Generate GraphQL schema from lexicon_ids and source files.""" + + output_content: str = "" + + # Append additional schemas if provided via --append-schema + if append_schema: + additional_schemas = read_schema_files(append_schema) + output_content += additional_schemas + + # Append the new schema. + output_content += "\n\n".join(generate_definitions(lexicon_ids)) + + # Write to output file + if output: + output_path: str = output + with open(output_path, "w") as f: + _ = f.write(output_content) + else: + print(output) + + +if __name__ == "__main__": + run(main) diff --git a/schema/schema-generated.graphql b/schema/schema-generated.graphql new file mode 100644 index 0000000..2b3b985 --- /dev/null +++ b/schema/schema-generated.graphql @@ -0,0 +1,402 @@ + +type AuthSession { + did: String! +} + +type AuthStatus { + success: Boolean! + session: AuthSession +} + +type InventoryItem { + id: ID! + title: String! + cost: String! + images: [String!]! + description: String! + location: String + condition: String + category: String + postedAt: String + owner: ID! + owner_profile: Lexicon_app_bsky_actor_defs_profileViewDetailed +} + +type PageInfo { + hasNextPage: Boolean! + hasPreviousPage: Boolean! + startCursor: String + endCursor: String +} + +type InventoryEdge { + cursor: String! + node: InventoryItem! +} + +type InventoryConnection { + edges: [InventoryEdge!]! + pageInfo: PageInfo! +} + +enum Sender { + them + me +} + +type Message { + id: ID! + sender: Sender! + text: String! + time: String! +} + +type Chat { + id: ID! + unread: Boolean! + sender: String! + avatar: String + messages: [Message!]! +} + +############# +# Notifications +############# + +type Notification { + id: ID! + title: String! + message: String! + time: String! + read: Boolean! +} + +############# +# Root object +############# + +type Query { + inventory( + first: Int + after: String + last: Int + before: String + category: String + location: String + condition: String + search: String + minPrice: Float + maxPrice: Float + ): InventoryConnection! + notifications: [Notification!]! + chats: [Chat!]! + user_notification_settings: UserNotificationSettings! + lexicon: Lexicon! +} + +type SessionDestroyResult { + success: Boolean! +} + +type Mutation { + session_retrieve: AuthSession + session_destroy: SessionDestroyResult + update_user_notification_settings( + enableAll: Boolean + listingUpdates: Boolean + userRequests: Boolean + messages: Boolean + promotions: Boolean + news: Boolean + ): UserNotificationSettings! + create_inventory( + title: String! + cost: String! + description: String! + location: String + condition: String + category: String + images: [String!]! + ): CreateInventoryResult! + authenticate(code: String!, state: String!, iss: String!, redirect_uri: String!): AuthStatus + trigger_session_deleted(did: String!): SessionDeletedTriggerResult! +} + +type SessionDeletedTriggerResult { + success: Boolean! + message: String! +} + +type CreateInventoryResult { + edge: InventoryEdge +} + +type UserNotificationSettings { + id: ID! + enableAll: Boolean! + listingUpdates: Boolean! + userRequests: Boolean! + messages: Boolean! + promotions: Boolean! + news: Boolean! +} + +type SessionDeletedPayload { + did: String! +} + +type Subscription { + session_deleted: SessionDeletedPayload! +} + +############# +# ATProto Lexicon +############# + +# app.bsky.actor.defs#profileViewDetailed +type Lexicon_app_bsky_actor_defs_profileViewDetailed { + did: ID! + handle: ID! + displayName: String + description: String + pronouns: String + website: String + avatar: String + banner: String + followersCount: Int + followsCount: Int + postsCount: Int + associated: Lexicon_app_bsky_actor_defs_profileAssociated + joinedViaStarterPack: Lexicon_app_bsky_graph_defs_starterPackViewBasic + indexedAt: String + createdAt: String + viewer: Lexicon_app_bsky_actor_defs_viewerState + labels: [Lexicon_com_atproto_label_defs_label!] + pinnedPost: Lexicon_com_atproto_repo_strongRef + verification: Lexicon_app_bsky_actor_defs_verificationState + status: Lexicon_app_bsky_actor_defs_statusView +} + + + +# com.atproto.server.getSessionOutput +type Lexicon_com_atproto_server_getSessionOutput { + handle: ID! + did: ID! + email: String + emailConfirmed: Boolean + emailAuthFactor: Boolean + active: Boolean + status: String +} + + + +# app.bsky.actor.defs#profileAssociated +type Lexicon_app_bsky_actor_defs_profileAssociated { + lists: Int + feedgens: Int + starterPacks: Int + labeler: Boolean + chat: Lexicon_app_bsky_actor_defs_profileAssociatedChat + activitySubscription: Lexicon_app_bsky_actor_defs_profileAssociatedActivitySubscription +} + + + +# app.bsky.graph.defs#starterPackViewBasic +type Lexicon_app_bsky_graph_defs_starterPackViewBasic { + uri: String! + cid: String! + creator: Lexicon_app_bsky_actor_defs_profileViewBasic! + listItemCount: Int + joinedWeekCount: Int + joinedAllTimeCount: Int + labels: [Lexicon_com_atproto_label_defs_label!] + indexedAt: String! +} + + + +# app.bsky.actor.defs#viewerState +type Lexicon_app_bsky_actor_defs_viewerState { + muted: Boolean + mutedByList: Lexicon_app_bsky_graph_defs_listViewBasic + blockedBy: Boolean + blocking: String + blockingByList: Lexicon_app_bsky_graph_defs_listViewBasic + following: String + followedBy: String + knownFollowers: Lexicon_app_bsky_actor_defs_knownFollowers + activitySubscription: Lexicon_app_bsky_notification_defs_activitySubscription +} + + + +# com.atproto.label.defs#label +type Lexicon_com_atproto_label_defs_label { + ver: Int + src: ID! + uri: String! + cid: String + val: String! + neg: Boolean + cts: String! + exp: String + sig: String +} + + + +# com.atproto.repo.strongRef +type Lexicon_com_atproto_repo_strongRef { + uri: String! + cid: String! +} + + + +# app.bsky.actor.defs#verificationState +type Lexicon_app_bsky_actor_defs_verificationState { + verifications: [Lexicon_app_bsky_actor_defs_verificationView!]! + verifiedStatus: String! + trustedVerifierStatus: String! +} + + + +# app.bsky.actor.defs#statusView +type Lexicon_app_bsky_actor_defs_statusView { + uri: String + cid: String + status: String! + expiresAt: String + isActive: Boolean + isDisabled: Boolean +} + + + +# app.bsky.actor.defs#profileAssociatedChat +type Lexicon_app_bsky_actor_defs_profileAssociatedChat { + allowIncoming: String! +} + + + +# app.bsky.actor.defs#profileAssociatedActivitySubscription +type Lexicon_app_bsky_actor_defs_profileAssociatedActivitySubscription { + allowSubscriptions: String! +} + + + +# app.bsky.actor.defs#profileViewBasic +type Lexicon_app_bsky_actor_defs_profileViewBasic { + did: ID! + handle: ID! + displayName: String + pronouns: String + avatar: String + associated: Lexicon_app_bsky_actor_defs_profileAssociated + viewer: Lexicon_app_bsky_actor_defs_viewerState + labels: [Lexicon_com_atproto_label_defs_label!] + createdAt: String + verification: Lexicon_app_bsky_actor_defs_verificationState + status: Lexicon_app_bsky_actor_defs_statusView +} + + + +# app.bsky.graph.defs#listViewBasic +type Lexicon_app_bsky_graph_defs_listViewBasic { + uri: String! + cid: String! + name: String! + purpose: Lexicon_app_bsky_graph_defs_listPurpose! + avatar: String + listItemCount: Int + labels: [Lexicon_com_atproto_label_defs_label!] + viewer: Lexicon_app_bsky_graph_defs_listViewerState + indexedAt: String +} + + + +# app.bsky.actor.defs#knownFollowers +type Lexicon_app_bsky_actor_defs_knownFollowers { + count: Int! + followers: [Lexicon_app_bsky_actor_defs_profileViewBasic!]! +} + + + +# app.bsky.notification.defs#activitySubscription +type Lexicon_app_bsky_notification_defs_activitySubscription { + post: Boolean! + reply: Boolean! +} + + + +# app.bsky.actor.defs#verificationView +type Lexicon_app_bsky_actor_defs_verificationView { + issuer: ID! + uri: String! + isValid: Boolean! + createdAt: String! +} + + + +# app.bsky.graph.defs#listPurpose +enum Lexicon_app_bsky_graph_defs_listPurpose { + APP_BSKY_GRAPH_DEFS_MODLIST + APP_BSKY_GRAPH_DEFS_CURATELIST + APP_BSKY_GRAPH_DEFS_REFERENCELIST +} + + + +# app.bsky.graph.defs#listViewerState +type Lexicon_app_bsky_graph_defs_listViewerState { + muted: Boolean + blocked: String +} + + + +type Lexicon_app_bsky_actor { + getProfile(actor: ID!): Lexicon_app_bsky_actor_defs_profileViewDetailed +} + + +type Lexicon_app_bsky { + actor: Lexicon_app_bsky_actor! +} + + +type Lexicon_app { + bsky: Lexicon_app_bsky! +} + + +type Lexicon_com_atproto_server { + getSession: Lexicon_com_atproto_server_getSessionOutput +} + + +type Lexicon_com_atproto { + server: Lexicon_com_atproto_server! +} + + +type Lexicon_com { + atproto: Lexicon_com_atproto! +} + + +type Lexicon { + app: Lexicon_app! + com: Lexicon_com! +} diff --git a/schema/schema-generated.graphql.json b/schema/schema-generated.graphql.json new file mode 100644 index 0000000..3cf3be7 --- /dev/null +++ b/schema/schema-generated.graphql.json @@ -0,0 +1 @@ +"\ntype AuthSession {\n did: String!\n}\n\ntype AuthStatus {\n success: Boolean!\n session: AuthSession\n}\n\ntype InventoryItem {\n id: ID!\n title: String!\n cost: String!\n images: [String!]!\n description: String!\n location: String\n condition: String\n category: String\n postedAt: String\n owner: ID!\n owner_profile: Lexicon_app_bsky_actor_defs_profileViewDetailed\n}\n\ntype PageInfo {\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n startCursor: String\n endCursor: String\n}\n\ntype InventoryEdge {\n cursor: String!\n node: InventoryItem!\n}\n\ntype InventoryConnection {\n edges: [InventoryEdge!]!\n pageInfo: PageInfo!\n}\n\nenum Sender {\n them\n me\n}\n\ntype Message {\n id: ID!\n sender: Sender!\n text: String!\n time: String!\n}\n\ntype Chat {\n id: ID!\n unread: Boolean!\n sender: String!\n avatar: String\n messages: [Message!]!\n}\n\n#############\n# Notifications\n#############\n\ntype Notification {\n id: ID!\n title: String!\n message: String!\n time: String!\n read: Boolean!\n}\n\n#############\n# Root object\n#############\n\ntype Query {\n inventory(\n first: Int\n after: String\n last: Int\n before: String\n category: String\n location: String\n condition: String\n search: String\n minPrice: Float\n maxPrice: Float\n ): InventoryConnection!\n notifications: [Notification!]!\n chats: [Chat!]!\n user_notification_settings: UserNotificationSettings!\n lexicon: Lexicon!\n}\n\ntype SessionDestroyResult {\n success: Boolean!\n}\n\ntype Mutation {\n session_retrieve: AuthSession\n session_destroy: SessionDestroyResult\n update_user_notification_settings(\n enableAll: Boolean\n listingUpdates: Boolean\n userRequests: Boolean\n messages: Boolean\n promotions: Boolean\n news: Boolean\n ): UserNotificationSettings!\n create_inventory(\n title: String!\n cost: String!\n description: String!\n location: String\n condition: String\n category: String\n images: [String!]!\n ): CreateInventoryResult!\n authenticate(code: String!, state: String!, iss: String!, redirect_uri: String!): AuthStatus\n trigger_session_deleted(did: String!): SessionDeletedTriggerResult!\n}\n\ntype SessionDeletedTriggerResult {\n success: Boolean!\n message: String!\n}\n\ntype CreateInventoryResult {\n edge: InventoryEdge\n}\n\ntype UserNotificationSettings {\n id: ID!\n enableAll: Boolean!\n listingUpdates: Boolean!\n userRequests: Boolean!\n messages: Boolean!\n promotions: Boolean!\n news: Boolean!\n}\n\ntype SessionDeletedPayload {\n did: String!\n}\n\ntype Subscription {\n session_deleted: SessionDeletedPayload!\n}\n\n#############\n# ATProto Lexicon\n#############\n\n# app.bsky.actor.defs#profileViewDetailed\ntype Lexicon_app_bsky_actor_defs_profileViewDetailed {\n did: ID!\n handle: ID!\n displayName: String\n description: String\n pronouns: String\n website: String\n avatar: String\n banner: String\n followersCount: Int\n followsCount: Int\n postsCount: Int\n associated: Lexicon_app_bsky_actor_defs_profileAssociated\n joinedViaStarterPack: Lexicon_app_bsky_graph_defs_starterPackViewBasic\n indexedAt: String\n createdAt: String\n viewer: Lexicon_app_bsky_actor_defs_viewerState\n labels: [Lexicon_com_atproto_label_defs_label!]\n pinnedPost: Lexicon_com_atproto_repo_strongRef\n verification: Lexicon_app_bsky_actor_defs_verificationState\n status: Lexicon_app_bsky_actor_defs_statusView\n}\n\n\n\n# com.atproto.server.getSessionOutput\ntype Lexicon_com_atproto_server_getSessionOutput {\n handle: ID!\n did: ID!\n email: String\n emailConfirmed: Boolean\n emailAuthFactor: Boolean\n active: Boolean\n status: String\n}\n\n\n\n# app.bsky.actor.defs#profileAssociated\ntype Lexicon_app_bsky_actor_defs_profileAssociated {\n lists: Int\n feedgens: Int\n starterPacks: Int\n labeler: Boolean\n chat: Lexicon_app_bsky_actor_defs_profileAssociatedChat\n activitySubscription: Lexicon_app_bsky_actor_defs_profileAssociatedActivitySubscription\n}\n\n\n\n# app.bsky.graph.defs#starterPackViewBasic\ntype Lexicon_app_bsky_graph_defs_starterPackViewBasic {\n uri: String!\n cid: String!\n creator: Lexicon_app_bsky_actor_defs_profileViewBasic!\n listItemCount: Int\n joinedWeekCount: Int\n joinedAllTimeCount: Int\n labels: [Lexicon_com_atproto_label_defs_label!]\n indexedAt: String!\n}\n\n\n\n# app.bsky.actor.defs#viewerState\ntype Lexicon_app_bsky_actor_defs_viewerState {\n muted: Boolean\n mutedByList: Lexicon_app_bsky_graph_defs_listViewBasic\n blockedBy: Boolean\n blocking: String\n blockingByList: Lexicon_app_bsky_graph_defs_listViewBasic\n following: String\n followedBy: String\n knownFollowers: Lexicon_app_bsky_actor_defs_knownFollowers\n activitySubscription: Lexicon_app_bsky_notification_defs_activitySubscription\n}\n\n\n\n# com.atproto.label.defs#label\ntype Lexicon_com_atproto_label_defs_label {\n ver: Int\n src: ID!\n uri: String!\n cid: String\n val: String!\n neg: Boolean\n cts: String!\n exp: String\n sig: String\n}\n\n\n\n# com.atproto.repo.strongRef\ntype Lexicon_com_atproto_repo_strongRef {\n uri: String!\n cid: String!\n}\n\n\n\n# app.bsky.actor.defs#verificationState\ntype Lexicon_app_bsky_actor_defs_verificationState {\n verifications: [Lexicon_app_bsky_actor_defs_verificationView!]!\n verifiedStatus: String!\n trustedVerifierStatus: String!\n}\n\n\n\n# app.bsky.actor.defs#statusView\ntype Lexicon_app_bsky_actor_defs_statusView {\n uri: String\n cid: String\n status: String!\n expiresAt: String\n isActive: Boolean\n isDisabled: Boolean\n}\n\n\n\n# app.bsky.actor.defs#profileAssociatedChat\ntype Lexicon_app_bsky_actor_defs_profileAssociatedChat {\n allowIncoming: String!\n}\n\n\n\n# app.bsky.actor.defs#profileAssociatedActivitySubscription\ntype Lexicon_app_bsky_actor_defs_profileAssociatedActivitySubscription {\n allowSubscriptions: String!\n}\n\n\n\n# app.bsky.actor.defs#profileViewBasic\ntype Lexicon_app_bsky_actor_defs_profileViewBasic {\n did: ID!\n handle: ID!\n displayName: String\n pronouns: String\n avatar: String\n associated: Lexicon_app_bsky_actor_defs_profileAssociated\n viewer: Lexicon_app_bsky_actor_defs_viewerState\n labels: [Lexicon_com_atproto_label_defs_label!]\n createdAt: String\n verification: Lexicon_app_bsky_actor_defs_verificationState\n status: Lexicon_app_bsky_actor_defs_statusView\n}\n\n\n\n# app.bsky.graph.defs#listViewBasic\ntype Lexicon_app_bsky_graph_defs_listViewBasic {\n uri: String!\n cid: String!\n name: String!\n purpose: Lexicon_app_bsky_graph_defs_listPurpose!\n avatar: String\n listItemCount: Int\n labels: [Lexicon_com_atproto_label_defs_label!]\n viewer: Lexicon_app_bsky_graph_defs_listViewerState\n indexedAt: String\n}\n\n\n\n# app.bsky.actor.defs#knownFollowers\ntype Lexicon_app_bsky_actor_defs_knownFollowers {\n count: Int!\n followers: [Lexicon_app_bsky_actor_defs_profileViewBasic!]!\n}\n\n\n\n# app.bsky.notification.defs#activitySubscription\ntype Lexicon_app_bsky_notification_defs_activitySubscription {\n post: Boolean!\n reply: Boolean!\n}\n\n\n\n# app.bsky.actor.defs#verificationView\ntype Lexicon_app_bsky_actor_defs_verificationView {\n issuer: ID!\n uri: String!\n isValid: Boolean!\n createdAt: String!\n}\n\n\n\n# app.bsky.graph.defs#listPurpose\nenum Lexicon_app_bsky_graph_defs_listPurpose {\n APP_BSKY_GRAPH_DEFS_MODLIST\n APP_BSKY_GRAPH_DEFS_CURATELIST\n APP_BSKY_GRAPH_DEFS_REFERENCELIST\n}\n\n\n\n# app.bsky.graph.defs#listViewerState\ntype Lexicon_app_bsky_graph_defs_listViewerState {\n muted: Boolean\n blocked: String\n}\n\n\n\ntype Lexicon_app_bsky_actor {\n getProfile(actor: ID!): Lexicon_app_bsky_actor_defs_profileViewDetailed\n}\n\n\ntype Lexicon_app_bsky {\n actor: Lexicon_app_bsky_actor!\n}\n\n\ntype Lexicon_app {\n bsky: Lexicon_app_bsky!\n}\n\n\ntype Lexicon_com_atproto_server {\n getSession: Lexicon_com_atproto_server_getSessionOutput\n}\n\n\ntype Lexicon_com_atproto {\n server: Lexicon_com_atproto_server!\n}\n\n\ntype Lexicon_com {\n atproto: Lexicon_com_atproto!\n}\n\n\ntype Lexicon {\n app: Lexicon_app!\n com: Lexicon_com!\n}\n" \ No newline at end of file diff --git a/schema/sources/types.graphql b/schema/sources/types.graphql new file mode 100644 index 0000000..aa94800 --- /dev/null +++ b/schema/sources/types.graphql @@ -0,0 +1,153 @@ +type AuthSession { + did: String! +} + +type AuthStatus { + success: Boolean! + session: AuthSession +} + +type InventoryItem { + id: ID! + title: String! + cost: String! + images: [String!]! + description: String! + location: String + condition: String + category: String + postedAt: String + owner: ID! + owner_profile: Lexicon_app_bsky_actor_defs_profileViewDetailed +} + +type PageInfo { + hasNextPage: Boolean! + hasPreviousPage: Boolean! + startCursor: String + endCursor: String +} + +type InventoryEdge { + cursor: String! + node: InventoryItem! +} + +type InventoryConnection { + edges: [InventoryEdge!]! + pageInfo: PageInfo! +} + +enum Sender { + them + me +} + +type Message { + id: ID! + sender: Sender! + text: String! + time: String! +} + +type Chat { + id: ID! + unread: Boolean! + sender: String! + avatar: String + messages: [Message!]! +} + +############# +# Notifications +############# + +type Notification { + id: ID! + title: String! + message: String! + time: String! + read: Boolean! +} + +############# +# Root object +############# + +type Query { + inventory( + first: Int + after: String + last: Int + before: String + category: String + location: String + condition: String + search: String + minPrice: Float + maxPrice: Float + ): InventoryConnection! + notifications: [Notification!]! + chats: [Chat!]! + user_notification_settings: UserNotificationSettings! + lexicon: Lexicon! +} + +type SessionDestroyResult { + success: Boolean! +} + +type Mutation { + session_retrieve: AuthSession + session_destroy: SessionDestroyResult + update_user_notification_settings( + enableAll: Boolean + listingUpdates: Boolean + userRequests: Boolean + messages: Boolean + promotions: Boolean + news: Boolean + ): UserNotificationSettings! + create_inventory( + title: String! + cost: String! + description: String! + location: String + condition: String + category: String + images: [String!]! + ): CreateInventoryResult! + authenticate(code: String!, state: String!, iss: String!, redirect_uri: String!): AuthStatus + trigger_session_deleted(did: String!): SessionDeletedTriggerResult! +} + +type SessionDeletedTriggerResult { + success: Boolean! + message: String! +} + +type CreateInventoryResult { + edge: InventoryEdge +} + +type UserNotificationSettings { + id: ID! + enableAll: Boolean! + listingUpdates: Boolean! + userRequests: Boolean! + messages: Boolean! + promotions: Boolean! + news: Boolean! +} + +type SessionDeletedPayload { + did: String! +} + +type Subscription { + session_deleted: SessionDeletedPayload! +} + +############# +# ATProto Lexicon +#############