diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..32b72c84 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,83 @@ +name: Docs + +on: + push: + branches: ["main"] + pull_request: + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + name: Build documentation + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: Install gomarkdoc + run: | + go install github.com/princjef/gomarkdoc/cmd/gomarkdoc@latest + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install MkDocs dependencies + run: | + python -m pip install --upgrade pip + if [ -f docs/requirements.txt ]; then + pip install -r docs/requirements.txt + elif [ -f requirements.txt ]; then + pip install -r requirements.txt + else + pip install mkdocs mkdocs-material + fi + + - name: Configure GitHub Pages + if: github.event_name != 'pull_request' + uses: actions/configure-pages@v5 + + - name: Generate Go API docs (gomarkdoc -> docs/*.md) + run: | + bash docs/generate_docs.sh + + - name: Build MkDocs site + run: | + mkdocs build --strict --site-dir site + + - name: Upload Pages artifact + if: github.event_name != 'pull_request' + uses: actions/upload-pages-artifact@v3 + with: + path: site + + deploy: + name: Deploy to GitHub Pages + if: github.event_name != 'pull_request' + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Deploy + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 6f27dc31..1694fa8c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .vscode .env +/site \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..f8bd967d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 PZSP2 Z1 Teams + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..c3f1e980 --- /dev/null +++ b/README.md @@ -0,0 +1,130 @@ +# Teams API wrapper Lib + +[![Go Reference](https://pkg.go.dev/badge/github.com/pzsp-teams/lib.svg)](https://pkg.go.dev/github.com/pzsp-teams/lib) +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) + +
+ +High-level Go (Golang) library that simplifies interaction with **Microsoft Graph API**. Provides abstraction over operations related to Teams, Channels, and Chats, adding a layer of automatic caching and name resolution. + +## 🚀 Key Features + +- **Simplified Authentication**: Built-in MSAL token support. +- **Intelligent Cache**: Automatic mapping of team names to IDs (e.g., "DevOps Team" -> `UUID`), reducing API queries. +- **Facade Architecture**: One main `Client` providing access to all services (`Teams`, `Channels`, `Chats`). +- **Type Safety**: All operations return strongly typed models. + +## 📦 Installation + +```bash +go get https://github.com/pzsp-teams/lib +``` + +## 🛠️ Architecture & Concepts + +The library uses a **Facade Pattern**. The Client struct aggregates domain-specific services: + +- **client.Teams**: Manage teams lifecycles and members. +- **client.Channels**: Manage standard and private channels. +- **client.Chats**: Handle messages and chat members. + +### The "Reference" concept + +Many methods accept a `_Ref` argument. This allows you to pass: + +- **UUID** +- **Display Name** (**email** address in case of UserRefs) - this provides convenient usage in interactive applications. + Library will automatically resolve refs to IDs. + +## 💻 Quick Start + +Full example usage is showcased [HERE](https://github.com/pzsp-teams/lib/tree/example-cmd-usage/cmd) +Below is a simple example showing how to initialize the client and list the current user's teams. + +### 1. Client initialization + +```go +import ( + "context" + "time" + "github.com/pzsp-teams/lib" + "github.com/pzsp-teams/lib/config" +) + +func main() { + ctx := context.Background() + + // Auth config (Azure AD) + authCfg := &config.AuthConfig{ + ClientID: "your-client-id", + Tenant: "your-tenant-id", + Email: "your-email", + Scopes: []string{"[https://graph.microsoft.com/.default](https://graph.microsoft.com/.default)"}, + AuthMethod: "DEVICE_CODE", // Or "INTERACTIVE" + } + + // Cache config + cacheCfg := &config.CacheConfig{ + Mode: config.CacheAsync, + Provider: config.CacheProviderJSONFile, // Local file cache + } + + // Client init + client, err := lib.NewClient(ctx, authCfg, nil, cacheCfg) + if err != nil { + panic(err) + } + defer lib.Close() // Important if using cache +} +``` + +### 2. Example usage + +```go +// List joined teams +teams, _ := client.Teams.ListMyJoined(ctx) +for _, t := range teams { + fmt.Printf("Team: %s (ID: %s)\n", t.DisplayName, t.ID) +} + +// Create a new team +newTeam, _ := client.Teams.CreateViaGroup(ctx, "Project Alpha", "project-alpha", "public") +``` + +## Authentication + +The library uses `config.AuthConfig` to establish the connection. Ensure your Azure App Registration has the necessary **API Permissions** (e.g., `Team.ReadBasic.All`, `Channel.ReadBasic.All`) granted in the Azure Portal. +Complete list of scopes required by all functions is available [HERE](https://github.com/pzsp-teams/lib/blob/example-cmd-usage/.env.template) + +There are two available ways to authenticate: + +- **INTERACTIVE** - log in window will automatically be opened within your browser. +- **DEVICE CODE** - library will provide you the **URL** and code, which need to be manually opened with browser of your choice. + +## Cache + +If enabled, stores metadata and non-sensitive mappings (e.g., `TeamRef` -> `UUID`) to provide efficient reference resolution. + +
+ +### ⚠️ Important: + +Because the cache might run background goroutines to keep data fresh, you **must** call lib.Close() when your application shuts down. This ensures all background operations complete and prevents memory leaks or race conditions. + +```go +defer lib.Close() // Important: closes global cache/background workers +``` + +## 📚 Documentation + +Full API reference, architecture details, and configuration guides are available here: + +👉 [Read the Documentation](https://pzsp-teams.github.io/lib/) + +## 📄 License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## Ports + +This library is also available in [Python](https://github.com/pzsp-teams/lib-python) diff --git a/client.go b/client.go index f51ac43e..601cd755 100644 --- a/client.go +++ b/client.go @@ -1,3 +1,21 @@ +// Package lib acts as the primary entry point for the Microsoft Teams API client library. +// It adopts a Facade pattern, aggregating specialized services (Teams, Channels, Chats) +// into a single, cohesive Client. +// +// The package manages the complexity of: +// - Authentication (via MSAL and Graph Token Providers). +// - Dependency Injection (wiring APIs, Caches, and Resolvers). +// - Caching strategies (transparently wrapping operations with caching layers). +// +// Usage: +// Initialize the Client using NewClient for a standard setup. +// Alternatively, if you need only specific services, use: +// - NewTeamServiceFromGraphClient for Teams service. +// - NewChannelServiceFromGraphClient for Channels service. +// - NewChatServiceFromGraphClient for Chats service. +// +// Always ensure to call Close() upon application shutdown to flush any background +// cache operations. package lib import ( @@ -15,14 +33,21 @@ import ( "github.com/pzsp-teams/lib/teams" ) +// Client is the central hub for interacting with the Microsoft Teams ecosystem. +// It aggregates access to specific domains: Channels, Teams, and Chats, hiding +// the complexity of underlying Graph API calls and caching mechanisms. type Client struct { Channels channels.Service Teams teams.Service Chats chats.Service } +// graphClient is a package-level singleton to hold the authenticated Graph client. +// Note: This approach assumes a single identity per application instance. var graphClient *graph.GraphServiceClient +// getGraphClient ensures a singleton instance of the GraphServiceClient is created. +// It initializes the MSAL token provider using the provided authentication config. func getGraphClient(authCfg *config.AuthConfig) (*graph.GraphServiceClient, error) { if graphClient == nil { tokenProvider, err := auth.GetMSALTokenProvider(authCfg) @@ -38,7 +63,9 @@ func getGraphClient(authCfg *config.AuthConfig) (*graph.GraphServiceClient, erro return graphClient, nil } -// NewClient will be used later +// NewClient initializes a new Client instance with fully configured internal services. +// It handles the authentication handshake using the provided authCfg and sets up +// sending and caching behaviors based on senderCfg and cacheCfg. func NewClient(ctx context.Context, authCfg *config.AuthConfig, senderCfg *config.SenderConfig, cacheCfg *config.CacheConfig) (*Client, error) { cl, err := getGraphClient(authCfg) if err != nil { @@ -48,6 +75,11 @@ func NewClient(ctx context.Context, authCfg *config.AuthConfig, senderCfg *confi return NewClientFromGraphClient(cl, senderCfg, cacheCfg) } +// NewClientFromGraphClient creates a Client using an existing, pre-configured GraphServiceClient. +// This is a separated exported constructor mainly for external testing purposes (via mocking Teams API by injection of GraphServiceClient). +// +// It wires up all internal dependencies, including API clients, caching layers, and +// entity resolvers (e.g., resolving team names to IDs). func NewClientFromGraphClient(graphClient *graph.GraphServiceClient, senderCfg *config.SenderConfig, cacheCfg *config.CacheConfig) (*Client, error) { teamsAPI := api.GetTeamAPI(graphClient, senderCfg) searchAPI := api.GetSearchAPI(graphClient, senderCfg) @@ -82,6 +114,8 @@ func NewClientFromGraphClient(graphClient *graph.GraphServiceClient, senderCfg * }, nil } +// NewChannelServiceFromGraphClient creates a standalone service for Channel operations. +// Use this if you do not need the full Client wrapper and only want to interact with Channels. func NewChannelServiceFromGraphClient(ctx context.Context, authCfg *config.AuthConfig, senderCfg *config.SenderConfig, cacheCfg *config.CacheConfig) (channels.Service, error) { cl, err := getGraphClient(authCfg) if err != nil { @@ -105,6 +139,8 @@ func NewChannelServiceFromGraphClient(ctx context.Context, authCfg *config.AuthC return channelSvc, nil } +// NewTeamServiceFromGraphClient creates a standalone service for Team operations. +// Use this if you do not need the full Client wrapper and only want to interact with Teams. func NewTeamServiceFromGraphClient(ctx context.Context, authCfg *config.AuthConfig, senderCfg *config.SenderConfig, cacheCfg *config.CacheConfig) (teams.Service, error) { cl, err := getGraphClient(authCfg) if err != nil { @@ -124,6 +160,8 @@ func NewTeamServiceFromGraphClient(ctx context.Context, authCfg *config.AuthConf return teamSvc, nil } +// NewChatServiceFromGraphClient creates a standalone service for Chat operations. +// Use this if you do not need the full Client wrapper and only want to interact with Chats. func NewChatServiceFromGraphClient(ctx context.Context, authCfg *config.AuthConfig, senderCfg *config.SenderConfig, cacheCfg *config.CacheConfig) (chats.Service, error) { cl, err := getGraphClient(authCfg) if err != nil { @@ -145,7 +183,9 @@ func NewChatServiceFromGraphClient(ctx context.Context, authCfg *config.AuthConf return chatSvc, nil } -// Close waits for all background operations to complete. +// Close ensures a graceful shutdown of the library. +// It waits for any pending background operations (such as asynchronous cache updates) +// to complete before returning, preventing data loss or race conditions. func Close() { if cacher.Singleton != nil { cacher.Singleton.Runner.Wait() diff --git a/docs/channels.md b/docs/channels.md new file mode 100644 index 00000000..53732fa5 --- /dev/null +++ b/docs/channels.md @@ -0,0 +1,120 @@ + + +# channels + +```go +import "github.com/pzsp-teams/lib/channels" +``` + +Package channels provides various channel\-related operations. It abstracts the underlying Microsoft Graph API calls. Package provides two services implementations \- one with cache and one without cache. Both can be instantiated and used interchangeably. If cache is enabled, the service will use a caching layer to store and retrieve channel/member references, improving performance and reducing API calls. Concepts: + +- Channels belong to teams. +- Channels can be standard or private. +- Users are identified by userID or email. +- Some operations require messageID \- these can be obtained via ListMessages. +- ChannelRef is a reference \(display name or ID\) to a channel used in method parameters. +- If teamRef or channelRef is a display and is not unique, an ambiguity error is returned. +- The authenticated user \(derived from MSAL\) is the one making the API calls \(appropriate scopes must be granted\). + +If an async cached service is used, call Wait\(\) to ensure all background cache updates are finished. + +## Index + +- [type Service](<#Service>) + - [func NewService\(ops channelOps, tr resolver.TeamResolver, cr resolver.ChannelResolver\) Service](<#NewService>) + + + +## type [Service]() + +Service defines the interface for channel\-related operations. It includes methods for managing channels, members, messages, and more. + +```go +type Service interface { + // ListChannels returns all channels in a team. + ListChannels(ctx context.Context, teamRef string) ([]*models.Channel, error) + + // Get retrieves a specific channel by its reference (ID or display name) within a team. + Get(ctx context.Context, teamRef, channelRef string) (*models.Channel, error) + + // CreateStandardChannel creates a standard channel within a team. + // Standard channels are open to all team members. + CreateStandardChannel(ctx context.Context, teamRef, name string) (*models.Channel, error) + + // CreatePrivateChannel creates a private channel within a team. + // Private channels are restricted to specific members. + // At least one owner must be specified. + CreatePrivateChannel(ctx context.Context, teamRef, name string, memberRefs, ownerRefs []string) (*models.Channel, error) + + // Delete removes a channel from a team. + Delete(ctx context.Context, teamRef, channelRef string) error + + // SendMessage sends a message to a channel. + // Body parameter is the body of the message. It includes: + // - Content: the text or html content of the message. + // - ContentType: the type of content (text or html). + // - Mentions: optional mentions to include in the message. + SendMessage(ctx context.Context, teamRef, channelRef string, body models.MessageBody) (*models.Message, error) + + // SendReply sends a reply to a specific message in a channel. + // Body parameter is the body of the reply message. It includes: + // - Content: the text or html content of the message. + // - ContentType: the type of content (text or html). + // - Mentions: optional mentions to include in the message. + SendReply(ctx context.Context, teamRef, channelRef, messageID string, body models.MessageBody) (*models.Message, error) + + // ListMessages returns one page of messages in a channel. + // + // NextLink in the returned MessageCollection can be used to retrieve the next page of messages. + ListMessages(ctx context.Context, teamRef, channelRef string, opts *models.ListMessagesOptions, includeSystem bool, nextLink *string) (*models.MessageCollection, error) + + // GetMessage retrieves a specific message from a channel by its ID. + GetMessage(ctx context.Context, teamRef, channelRef, messageID string) (*models.Message, error) + + // ListReplies returns one page of replies to a specific message in a channel. + // + // NextLink in the returned MessageCollection can be used to retrieve the next page of replies. + ListReplies(ctx context.Context, teamRef, channelRef, messageID string, top *int32, includeSystem bool, nextLink *string) (*models.MessageCollection, error) + + // GetReply retrieves a specific reply to a message in a channel by its ID. + GetReply(ctx context.Context, teamRef, channelRef, messageID, replyID string) (*models.Message, error) + + // ListMembers returns all members of a channel. + ListMembers(ctx context.Context, teamRef, channelRef string) ([]*models.Member, error) + + // AddMember adds a user to a channel. + AddMember(ctx context.Context, teamRef, channelRef, userRef string, isOwner bool) (*models.Member, error) + + // UpdateMemberRoles updates the roles of a member in a channel. + UpdateMemberRoles(ctx context.Context, teamRef, channelRef, userRef string, isOwner bool) (*models.Member, error) + + // RemoveMember removes a user from a channel. + RemoveMember(ctx context.Context, teamRef, channelRef, userRef string) error + + // GetMentions resolves raw mention strings to Mention objects in the context of a channel. Raw mentions can be: + // - Emails + // - Channel (only the same channel as channelRef can be mentioned). It can be used by specifying "channel" or channel display name as raw mention. + // - Team (only the parent team of the channel can be mentioned). It can be used by specifying "team" or team display name as raw mention. + // - User IDs + GetMentions(ctx context.Context, teamRef, channelRef string, rawMentions []string) ([]models.Mention, error) + + // SearchMessagesInChannel searches for messages in a channel matching the specified query and options. + // + // If channelRef is nil, searches across all channels the user has access to. + // If teamRef is also nil, searches across all teams and channels the user has access to. + // + // Returns search results containing matching messages. + SearchMessages(ctx context.Context, teamRef, channelRef *string, opts *search.SearchMessagesOptions, searchConfig *search.SearchConfig) (*search.SearchResults, error) +} +``` + + +### func [NewService]() + +```go +func NewService(ops channelOps, tr resolver.TeamResolver, cr resolver.ChannelResolver) Service +``` + +NewService creates a new channels Service instance + +Generated by [gomarkdoc]() diff --git a/docs/chats.md b/docs/chats.md new file mode 100644 index 00000000..ec8ccd93 --- /dev/null +++ b/docs/chats.md @@ -0,0 +1,163 @@ + + +# chats + +```go +import "github.com/pzsp-teams/lib/chats" +``` + +Package chats provides various chat\-related operations. It abstracts the underlying Microsoft Graph API calls. Package provides two services implementations \- one with cache and one without cache. Both can be instantiated and used interchangeably. If cache is enabled, the service will use a caching layer to store and retrieve chat/member references, improving performance and reducing API calls. Concepts: + +- Chats are either one\-on\-one or group chats. +- Users are identified by userID or email. +- Some operations require messageID \- these can be obtained via ListMessages. +- ChatRef and GroupChatRef are references to chats used in method parameters. +- If chatRef is a topic and is not unique, an ambiguity error is returned. +- The authenticated user \(derived from MSAL\) is the one making the API calls \(appropriate scopes must be granted\). + +If an async cached service is used, call Wait\(\) to ensure all background cache updates are finished. + +## Index + +- [type ChatRef](<#ChatRef>) +- [type GroupChatRef](<#GroupChatRef>) +- [type OneOnOneChatRef](<#OneOnOneChatRef>) +- [type Service](<#Service>) + - [func NewService\(chatOps chatOps, cr resolver.ChatResolver\) Service](<#NewService>) + + + +## type [ChatRef]() + +ChatRef is an interface representing a reference to a chat, which can be either a group chat or a one\-on\-one chat. + +```go +type ChatRef interface { + // contains filtered or unexported methods +} +``` + + +## type [GroupChatRef]() + +GroupChatRef identifies a group chat. It may reference a chat by: + +- unique chatID + +- chat topic + +Note: Using chat topic may lead to ambiguities \(which must be resolved manually\) if multiple group chats share the same topic. + +```go +type GroupChatRef struct { + Ref string +} +``` + + +## type [OneOnOneChatRef]() + +OneOnOneChatRef identifies a one\-on\-one chat. It may reference a chat by: + +- unique chatID + +- recipient's reference \(userID, email\) + +Note: Chat must be established between the logged\-in user and the recipient for resolution to succeed. + +```go +type OneOnOneChatRef struct { + Ref string +} +``` + + +## type [Service]() + +Service defines the interface for chat\-related operations. It includes methods for creating chats, managing members, sending messages, and more. + +```go +type Service interface { + // CreateOneOnOne creates a one-on-one chat with the given recipient. + // The authenticated user is automatically added to the chat. + CreateOneOnOne(ctx context.Context, recipientRef string) (*models.Chat, error) + + // CreateGroup creates a group chat with the given recipients and topic. + // The authenticated user may be included by setting includeMe to true. + CreateGroup(ctx context.Context, recipientRefs []string, topic string, includeMe bool) (*models.Chat, error) + + // GetChat retrieves a chat (one-on-one or group) by its reference. + GetChat(ctx context.Context, chatRef ChatRef) (*models.Chat, error) + + // AddMemberToGroupChat adds a user to a group chat. + AddMemberToGroupChat(ctx context.Context, chatRef GroupChatRef, userRef string) (*models.Member, error) + + // RemoveMemberFromGroupChat removes a user from a group chat. + RemoveMemberFromGroupChat(ctx context.Context, chatRef GroupChatRef, userRef string) error + + // ListGroupChatMembers returns all members of a group chat. + ListGroupChatMembers(ctx context.Context, chatRef GroupChatRef) ([]*models.Member, error) + + // UpdateGroupChatTopic updates the topic of a group chat. + UpdateGroupChatTopic(ctx context.Context, chatRef GroupChatRef, topic string) (*models.Chat, error) + + // ListMessages returns all messages in a chat. + // + // NextLink in the returned MessageCollection can be used to retrieve the next page of messages. + ListMessages(ctx context.Context, chatRef ChatRef, includeSystem bool, nextLink *string) (*models.MessageCollection, error) + + // SendMessage sends a message to a chat. + // Body parameter is the body of the message. It includes: + // - Content: the text or html content of the message. + // - ContentType: the type of content (text or html). + // - Mentions: optional mentions to include in the message. + SendMessage(ctx context.Context, chatRef ChatRef, body models.MessageBody) (*models.Message, error) + + // DeleteMessage deletes a message from a chat. Action is reversible - soft delete is performed. + DeleteMessage(ctx context.Context, chatRef ChatRef, messageID string) error + + // GetMessage retrieves a specific message from a chat by its ID. + GetMessage(ctx context.Context, chatRef ChatRef, messageID string) (*models.Message, error) + + // ListChats returns all chats, optionally filtered by chat type. + ListChats(ctx context.Context, chatType *models.ChatType) ([]*models.Chat, error) + + // ListAllMessages returns all messages in all chats within the specified time range. Top limits the number of messages returned. + // + // Note: This operation does not work in delegated permission mode. + ListAllMessages(ctx context.Context, startTime, endTime *time.Time, top *int32) ([]*models.Message, error) + + // ListPinnedMessages returns all pinned messages in a chat. + ListPinnedMessages(ctx context.Context, chatRef ChatRef) ([]*models.Message, error) + + // PinMessage pins a message in a chat. + PinMessage(ctx context.Context, chatRef ChatRef, messageID string) error + + // UnpinMessage unpins a message in a chat. + UnpinMessage(ctx context.Context, chatRef ChatRef, pinnedMessageID string) error + + // GetMentions resolves raw mention strings to Mention objects in the context of a chat. Raw mentions can be: + // - Emails + // - Everyone (for group chats) + // - User IDs + GetMentions(ctx context.Context, chatRef ChatRef, rawMentions []string) ([]models.Mention, error) + + // SearchMessages searches for messages in a chat matching the specified query and options. + // + // If chatRef is nil, searches across all chats the user has access to. + // + // Returns search results containing matching messages. + SearchMessages(ctx context.Context, chatRef ChatRef, opts *search.SearchMessagesOptions, searchConfig *search.SearchConfig) (*search.SearchResults, error) +} +``` + + +### func [NewService]() + +```go +func NewService(chatOps chatOps, cr resolver.ChatResolver) Service +``` + +NewService creates a new instance of the chat service. + +Generated by [gomarkdoc]() diff --git a/docs/client.md b/docs/client.md new file mode 100644 index 00000000..f31f27ed --- /dev/null +++ b/docs/client.md @@ -0,0 +1,105 @@ + + +# lib + +```go +import "github.com/pzsp-teams/lib" +``` + +Package lib acts as the primary entry point for the Microsoft Teams API client library. It adopts a Facade pattern, aggregating specialized services \(Teams, Channels, Chats\) into a single, cohesive Client. + +The package manages the complexity of: + +- Authentication \(via MSAL and Graph Token Providers\). +- Dependency Injection \(wiring APIs, Caches, and Resolvers\). +- Caching strategies \(transparently wrapping operations with caching layers\). + +Usage: Initialize the Client using NewClient for a standard setup. Alternatively, if you need only specific services, use: + +- NewTeamServiceFromGraphClient for Teams service. +- NewChannelServiceFromGraphClient for Channels service. +- NewChatServiceFromGraphClient for Chats service. + +Always ensure to call Close\(\) upon application shutdown to flush any background cache operations. + +## Index + +- [func Close\(\)](<#Close>) +- [func NewChannelServiceFromGraphClient\(ctx context.Context, authCfg \*config.AuthConfig, senderCfg \*config.SenderConfig, cacheCfg \*config.CacheConfig\) \(channels.Service, error\)](<#NewChannelServiceFromGraphClient>) +- [func NewChatServiceFromGraphClient\(ctx context.Context, authCfg \*config.AuthConfig, senderCfg \*config.SenderConfig, cacheCfg \*config.CacheConfig\) \(chats.Service, error\)](<#NewChatServiceFromGraphClient>) +- [func NewTeamServiceFromGraphClient\(ctx context.Context, authCfg \*config.AuthConfig, senderCfg \*config.SenderConfig, cacheCfg \*config.CacheConfig\) \(teams.Service, error\)](<#NewTeamServiceFromGraphClient>) +- [type Client](<#Client>) + - [func NewClient\(ctx context.Context, authCfg \*config.AuthConfig, senderCfg \*config.SenderConfig, cacheCfg \*config.CacheConfig\) \(\*Client, error\)](<#NewClient>) + - [func NewClientFromGraphClient\(graphClient \*graph.GraphServiceClient, senderCfg \*config.SenderConfig, cacheCfg \*config.CacheConfig\) \(\*Client, error\)](<#NewClientFromGraphClient>) + + + +## func [Close]() + +```go +func Close() +``` + +Close ensures a graceful shutdown of the library. It waits for any pending background operations \(such as asynchronous cache updates\) to complete before returning, preventing data loss or race conditions. + + +## func [NewChannelServiceFromGraphClient]() + +```go +func NewChannelServiceFromGraphClient(ctx context.Context, authCfg *config.AuthConfig, senderCfg *config.SenderConfig, cacheCfg *config.CacheConfig) (channels.Service, error) +``` + +NewChannelServiceFromGraphClient creates a standalone service for Channel operations. Use this if you do not need the full Client wrapper and only want to interact with Channels. + + +## func [NewChatServiceFromGraphClient]() + +```go +func NewChatServiceFromGraphClient(ctx context.Context, authCfg *config.AuthConfig, senderCfg *config.SenderConfig, cacheCfg *config.CacheConfig) (chats.Service, error) +``` + +NewChatServiceFromGraphClient creates a standalone service for Chat operations. Use this if you do not need the full Client wrapper and only want to interact with Chats. + + +## func [NewTeamServiceFromGraphClient]() + +```go +func NewTeamServiceFromGraphClient(ctx context.Context, authCfg *config.AuthConfig, senderCfg *config.SenderConfig, cacheCfg *config.CacheConfig) (teams.Service, error) +``` + +NewTeamServiceFromGraphClient creates a standalone service for Team operations. Use this if you do not need the full Client wrapper and only want to interact with Teams. + + +## type [Client]() + +Client is the central hub for interacting with the Microsoft Teams ecosystem. It aggregates access to specific domains: Channels, Teams, and Chats, hiding the complexity of underlying Graph API calls and caching mechanisms. + +```go +type Client struct { + Channels channels.Service + Teams teams.Service + Chats chats.Service +} +``` + + +### func [NewClient]() + +```go +func NewClient(ctx context.Context, authCfg *config.AuthConfig, senderCfg *config.SenderConfig, cacheCfg *config.CacheConfig) (*Client, error) +``` + +NewClient initializes a new Client instance with fully configured internal services. It handles the authentication handshake using the provided authCfg and sets up sending and caching behaviors based on senderCfg and cacheCfg. + + +### func [NewClientFromGraphClient]() + +```go +func NewClientFromGraphClient(graphClient *graph.GraphServiceClient, senderCfg *config.SenderConfig, cacheCfg *config.CacheConfig) (*Client, error) +``` + +NewClientFromGraphClient creates a Client using an existing, pre\-configured GraphServiceClient. This is a separated exported constructor mainly for external testing purposes \(via mocking Teams API by injection of GraphServiceClient\). + +It wires up all internal dependencies, including API clients, caching layers, and entity resolvers \(e.g., resolving team names to IDs\). + +Generated by [gomarkdoc]() diff --git a/docs/config.md b/docs/config.md new file mode 100644 index 00000000..f1331b66 --- /dev/null +++ b/docs/config.md @@ -0,0 +1,129 @@ + + +# config + +```go +import "github.com/pzsp-teams/lib/config" +``` + +Package config holds configuration structs used across the application. Defined configs: + +- AuthConfig: holds authentication configuration. +- SenderConfig: holds sender configuration. +- CacheConfig: holds caching configuration. + +## Index + +- [type AuthConfig](<#AuthConfig>) +- [type CacheConfig](<#CacheConfig>) +- [type CacheMode](<#CacheMode>) +- [type CacheProvider](<#CacheProvider>) +- [type Method](<#Method>) +- [type SenderConfig](<#SenderConfig>) + + + +## type [AuthConfig]() + +AuthConfig holds configuration for authentication. All fields are required \- they are needed to acquire tokens via MSAL. + +```go +type AuthConfig struct { + ClientID string + Tenant string + Email string + Scopes []string + AuthMethod Method +} +``` + + +## type [CacheConfig]() + +CacheConfig holds configuration for caching. + +```go +type CacheConfig struct { + Mode CacheMode + Provider CacheProvider + Path *string +} +``` + + +## type [CacheMode]() + +CacheMode defines the caching strategy used by the application. + +```go +type CacheMode string +``` + + + +```go +const ( + // CacheDisabled indicates that caching is turned off. + CacheDisabled CacheMode = "DISABLED" + + // CacheSync indicates that cache operations are performed synchronously. + CacheSync CacheMode = "SYNC" + + // CacheAsync indicates that cache operations are performed asynchronously. + CacheAsync CacheMode = "ASYNC" +) +``` + + +## type [CacheProvider]() + +CacheProvider defines the backend used for caching. + +```go +type CacheProvider string +``` + + + +```go +const ( + // CacheProviderJSONFile indicates that json-file cache is used. + CacheProviderJSONFile CacheProvider = "JSON_FILE" +) +``` + + +## type [Method]() + +Method defines the authentication flow used when acquiring tokens. + +```go +type Method string +``` + + + +```go +const ( + // Interactive opens a browser window for user authentication. + Interactive Method = "INTERACTIVE" + + // DeviceCode prints a device code to the console and prompts the user to visit a URL to authenticate. + DeviceCode Method = "DEVICE_CODE" +) +``` + + +## type [SenderConfig]() + +SenderConfig defines configuration for the request sender which connects with the Microsoft Graph API. MaxRetryDelay and Timeout are in seconds. + +```go +type SenderConfig struct { + MaxRetries int + NextRetryDelay int + Timeout int +} +``` + +Generated by [gomarkdoc]() diff --git a/docs/generate_docs.sh b/docs/generate_docs.sh new file mode 100755 index 00000000..de2e92c5 --- /dev/null +++ b/docs/generate_docs.sh @@ -0,0 +1,7 @@ +$(go env GOPATH)/bin/gomarkdoc ./teams --output docs/teams.md +$(go env GOPATH)/bin/gomarkdoc ./channels --output docs/channels.md +$(go env GOPATH)/bin/gomarkdoc ./chats --output docs/chats.md +$(go env GOPATH)/bin/gomarkdoc ./models --output docs/models.md +$(go env GOPATH)/bin/gomarkdoc ./config --output docs/config.md +$(go env GOPATH)/bin/gomarkdoc ./search --output docs/search.md +$(go env GOPATH)/bin/gomarkdoc . --output docs/client.md \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..5863398c --- /dev/null +++ b/docs/index.md @@ -0,0 +1,117 @@ +# Teams API wrapper Lib + +[![Go Reference](https://pkg.go.dev/badge/github.com/pzsp-teams/lib.svg)](https://pkg.go.dev/github.com/pzsp-teams/lib) +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) + +
+ +High-level Go (Golang) library that simplifies interaction with **Microsoft Graph API**. Provides abstraction over operations related to Teams, Channels, and Chats, adding a layer of automatic caching and name resolution. + +## 🚀 Key Features + +- **Simplified Authentication**: Built-in MSAL token support. +- **Intelligent Cache**: Automatic mapping of team names to IDs (e.g., "DevOps Team" -> `UUID`), reducing API queries. +- **Facade Architecture**: One main `Client` providing access to all services (`Teams`, `Channels`, `Chats`). +- **Type Safety**: All operations return strongly typed models. + +## 📦 Installation + +```bash +go get (https://github.com/pzsp-teams/lib) +``` + +## 🛠️ Architecture & Concepts + +The library uses a **Facade Pattern**. The Client struct aggregates domain-specific services: + +- **client.Teams**: Manage teams lifecycles and members. +- **client.Channels**: Manage standard and private channels. +- **client.Chats**: Handle messages and chat members. + +### The "Reference" concept + +Many methods accept a `_Ref` argument. This allows you to pass: + +- **UUID** +- **Display Name** (**email** for UserRefs) - this provides convenient usage in interactive applications. + Library will automatically resolve refs to IDs. + +## 💻 Quick Start + +Full example usage is showcased [HERE](https://github.com/pzsp-teams/lib/tree/example-cmd-usage/cmd) +Here is a simple example of how to initialize the client and list the current user's teams. + +### 1. Client init + +```go +import ( + "context" + "time" + "[github.com/pzsp-teams/lib](https://github.com/pzsp-teams/lib)" + "[github.com/pzsp-teams/lib/config](https://github.com/pzsp-teams/lib/config)" +) + +func main() { + ctx := context.Background() + + // Auth config (Azure AD) + authCfg := &config.AuthConfig{ + ClientID: "your-client-id", + Tenant: "your-tenant-id", + Email: "your-email", + Scopes: []string{"[https://graph.microsoft.com/.default](https://graph.microsoft.com/.default)"}, + AuthMethod: "DEVICE_CODE", // Or "INTERACTIVE" + } + + // Cache config + cacheCfg := &config.CacheConfig{ + Mode: config.CacheAsync, + Provider: config.CacheProviderJSONFile, // Local file cache + } + + // Client init + client, err := lib.NewClient(ctx, authCfg, nil, cacheCfg) + if err != nil { + panic(err) + } + defer lib.Close() // Important if using cache +} +``` + +### 2. Example usage + +```go +// List joined teams +teams, _ := client.Teams.ListMyJoined(ctx) +for _, t := range teams { + fmt.Printf("Team: %s (ID: %s)\n", t.DisplayName, t.ID) +} + +// Create a new team +newTeam, _ := client.Teams.CreateViaGroup(ctx, "Project Alpha", "project-alpha", "public") +``` + +## Authentication + +The library uses `config.AuthConfig` to establish the connection.Ensure your Azure App Registration has the necessary **API Permissions** (e.g., `Team.ReadBasic.All`, `Channel.ReadBasic.All`) granted in the Azure Portal. +Complete list of scopes required by all functions is available [HERE](https://github.com/pzsp-teams/lib/blob/example-cmd-usage/.env.template) + +There are two available ways to authenticate: + +- **INTERACTIVE** - log in window will automatically be opened within your browser. +- **DEVICE CODE** - library will provide you the **URL** and code, which need to be manually opened with browser of your choice. + +## Cache + +If enabled, stores metadata and non-sensitive mappings, (e.g., `TeamRef` -> `UUID`) to provide efficient reference resolution. + +
+ +### ⚠️ Important: + +Because the cache might run background goroutines to keep data fresh, you **must** call lib.Close() when your application shuts down. This ensures all background operations complete and prevents memory leaks or race conditions. + +```go +defer lib.Close() +``` + diff --git a/docs/models.md b/docs/models.md new file mode 100644 index 00000000..051c620a --- /dev/null +++ b/docs/models.md @@ -0,0 +1,242 @@ + + +# models + +```go +import "github.com/pzsp-teams/lib/models" +``` + +Package models contains simplified Microsoft Teams domain types used by this library. + +## Index + +- [type Channel](<#Channel>) +- [type Chat](<#Chat>) +- [type ChatType](<#ChatType>) +- [type ListMessagesOptions](<#ListMessagesOptions>) +- [type Member](<#Member>) +- [type Mention](<#Mention>) +- [type MentionKind](<#MentionKind>) +- [type Message](<#Message>) +- [type MessageBody](<#MessageBody>) +- [type MessageCollection](<#MessageCollection>) +- [type MessageContentType](<#MessageContentType>) +- [type MessageFrom](<#MessageFrom>) +- [type Team](<#Team>) +- [type TeamUpdate](<#TeamUpdate>) + + + +## type [Channel]() + +Channel represents a Microsoft Teams channel. + +```go +type Channel struct { + ID string + Name string + IsGeneral bool +} +``` + + +## type [Chat]() + +Chat represents a chat in Microsoft Teams. + +```go +type Chat struct { + ID string + Type ChatType + IsHidden bool + Topic *string +} +``` + + +## type [ChatType]() + +ChatType represents the type of chat in Microsoft Teams. Type can be either one\-on\-one or group chat + +```go +type ChatType string +``` + + + +```go +const ( + // ChatTypeOneOnOne represents a one-on-one chat. + ChatTypeOneOnOne ChatType = "one-on-one" + // ChatTypeGroup represents a group chat. + ChatTypeGroup ChatType = "group" +) +``` + + +## type [ListMessagesOptions]() + +ListMessagesOptions contains options for listing messages. + +```go +type ListMessagesOptions struct { + Top *int32 + ExpandReplies bool +} +``` + + +## type [Member]() + +Member represents a member of a Microsoft Teams channel, direct chat or team. + +```go +type Member struct { + ID string + UserID string + DisplayName string + Role string + Email string +} +``` + + +## type [Mention]() + +Mention represents a mention in a Microsoft Teams message. + +```go +type Mention struct { + Kind MentionKind + AtID int32 + Text string + TargetID string +} +``` + + +## type [MentionKind]() + +MentionKind represents the kind of mention in a Microsoft Teams message. + +```go +type MentionKind string +``` + + + +```go +const ( + // MentionUser represents a user mention. + MentionUser MentionKind = "user" + // MentionChannel represents a channel mention. + MentionChannel MentionKind = "channel" + // MentionTeam represents a team mention. + MentionTeam MentionKind = "team" + // MentionEveryone represents an everyone mention - applicable to group chats only + MentionEveryone MentionKind = "everyone" +) +``` + + +## type [Message]() + +Message represents a Microsoft Teams chat message. It can be used in both chats and channels. + +```go +type Message struct { + ID string + Content string + ContentType MessageContentType + CreatedDateTime time.Time + From *MessageFrom + ReplyCount int +} +``` + + +## type [MessageBody]() + +MessageBody represents the body of a message in Microsoft Teams. + +```go +type MessageBody struct { + Content string + ContentType MessageContentType + Mentions []Mention +} +``` + + +## type [MessageCollection]() + +MessageCollection represents a collection of messages, potentially with a link to the next page of results. + +```go +type MessageCollection struct { + Messages []*Message + NextLink *string +} +``` + + +## type [MessageContentType]() + +MessageContentType represents the type of content in a Microsoft Teams message. It can be either text or HTML. + +```go +type MessageContentType string +``` + + + +```go +const ( + // MessageContentTypeText represents plain text content. + MessageContentTypeText MessageContentType = "text" + // MessageContentTypeHTML represents HTML content. + MessageContentTypeHTML MessageContentType = "html" +) +``` + + +## type [MessageFrom]() + +MessageFrom represents the sender of a message in Microsoft Teams. + +```go +type MessageFrom struct { + UserID string + DisplayName string +} +``` + + +## type [Team]() + +Team represents a Microsoft Teams team. + +```go +type Team struct { + ID string + DisplayName string + Description string + IsArchived bool + Visibility *string +} +``` + + +## type [TeamUpdate]() + +TeamUpdate represents the fields that can be updated for a Team. + +```go +type TeamUpdate struct { + DisplayName *string + Description *string + Visibility *string +} +``` + +Generated by [gomarkdoc]() diff --git a/docs/search.md b/docs/search.md new file mode 100644 index 00000000..35c9c4ea --- /dev/null +++ b/docs/search.md @@ -0,0 +1,179 @@ + + +# search + +```go +import "github.com/pzsp-teams/lib/search" +``` + +## Index + +- [type SearchConfig](<#SearchConfig>) + - [func DefaultSearchConfig\(\) \*SearchConfig](<#DefaultSearchConfig>) +- [type SearchMessagesOptions](<#SearchMessagesOptions>) +- [type SearchPage](<#SearchPage>) +- [type SearchResult](<#SearchResult>) +- [type SearchResults](<#SearchResults>) +- [type TimeInterval](<#TimeInterval>) + + + +## type [SearchConfig]() + +SearchConfig holds configuration for search operations. + +MaxWorkers specifies the maximum number of concurrent workers to use when fetching search results. A higher number can speed up searches but may increase resource usage. Default is 8. + +```go +type SearchConfig struct { + MaxWorkers int +} +``` + + +### func [DefaultSearchConfig]() + +```go +func DefaultSearchConfig() *SearchConfig +``` + +DefaultSearchConfig returns a SearchConfig with default settings. + + +## type [SearchMessagesOptions]() + +SearchMessagesOptions contains options for searching messages. + +Fields: + +- Query: The search query string. +- SearchPage: Pagination options. +- From: List of sender email addresses to include. +- NotFrom: List of sender email addresses to exclude. +- IsRead: Filter by read status \(true for read, false for unread\). +- IsMentioned: Filter by mention status \(true for mentioned, false for not mentioned\). +- To: List of recipient email addresses to include. +- NotTo: List of recipient email addresses to exclude. +- StartTime: Start time for the sent time range filter. +- EndTime: End time for the sent time range filter. +- Interval: Predefined time interval for the sent time filter. +- NotFromMe: Exclude messages sent by the current user. +- NotToMe: Exclude messages sent to the current user. +- FromMe: Include only messages sent by the current user. +- ToMe: Include only messages sent to the current user. + +Note: If Interval is set, it takes precedence over StartTime and EndTime. + +Note: Using \`to\` clauses works only in chats, not in team channels. + +Note: Currently, the queries for IsRead may not function as expected due to API limitations. + +```go +type SearchMessagesOptions struct { + Query *string + SearchPage *SearchPage + From []string + NotFrom []string + IsRead *bool + IsMentioned *bool + To []string + NotTo []string + StartTime *time.Time + EndTime *time.Time + Interval *TimeInterval + NotFromMe bool + NotToMe bool + FromMe bool + ToMe bool +} +``` + + +## type [SearchPage]() + +SearchPage contains pagination options for searching messages. Fields: + +- From: The starting index of the search results. +- Size: The number of results to return. + +Note: If not set, default pagination values will be used. From is zero\-based. Default Size is typically 25 + +```go +type SearchPage struct { + From *int32 + Size *int32 +} +``` + + +## type [SearchResult]() + +SearchResult represents a single search result containing a message and its context. + +Fields: + +- Message: The chat message. +- ChannelID: The ID of the channel where the message was found \(if applicable\). +- TeamID: The ID of the team where the message was found \(if applicable\). +- ChatID: The ID of the chat where the message was found \(if applicable\). + +```go +type SearchResult struct { + Message *models.Message + ChannelID *string + TeamID *string + ChatID *string +} +``` + + +## type [SearchResults]() + +SearchResults represents the results of a message search. + +Fields: + +- Messages: A list of search results. +- NextFrom: The pagination token for the next page of results \(if applicable\). + +```go +type SearchResults struct { + Messages []*SearchResult + NextFrom *int32 +} +``` + + +## type [TimeInterval]() + +TimeInterval represents a predefined time interval for message searches. + +Possible values include: + +- Today +- Yesterday +- ThisWeek +- ThisMonth +- LastMonth +- ThisYear +- LastYear + +```go +type TimeInterval string +``` + + + +```go +const ( + Today TimeInterval = "today" + Yesterday TimeInterval = "yesterday" + ThisWeek TimeInterval = "this week" + ThisMonth TimeInterval = "this month" + LastMonth TimeInterval = "last month" + ThisYear TimeInterval = "this year" + LastYear TimeInterval = "last year" +) +``` + +Generated by [gomarkdoc]() diff --git a/docs/teams.md b/docs/teams.md new file mode 100644 index 00000000..d0838e3d --- /dev/null +++ b/docs/teams.md @@ -0,0 +1,89 @@ + + +# teams + +```go +import "github.com/pzsp-teams/lib/teams" +``` + +Package teams provides team\-related operations and abstracts the underlying Microsoft Graph API calls. + +The package exposes two interchangeable service implementations: one without cache and one with cache. When cache is enabled, the service stores and reuses team references \(e.g. display name \-\> team ID\), reducing the number of resolver/API calls. The cache may be cleared on request errors. + +Concepts: + +- teamRef is a team reference \(ID or display name\) used in method parameters. +- Operations are executed on behalf of the authenticated user \(derived from MSAL\); required scopes must be granted. +- Some operations accept a Graph patch object \(msmodels.Team\) for updates. +- Archived teams can be archived/unarchived via dedicated operations. +- Deleted teams can be restored using a deleted group ID. + +If an async cached service is used, call Wait\(\) to ensure all background cache updates are finished. + +## Index + +- [type Service](<#Service>) + - [func NewService\(teamOps teamsOps, tr resolver.TeamResolver\) Service](<#NewService>) + + + +## type [Service]() + +Service defines the interface for team\-related operations. It includes methods for retrieving, creating, updating, archiving, unarchiving, deleting, and restoring teams. + +```go +type Service interface { + // Get retrieves a specific team by its reference (ID or display name). + Get(ctx context.Context, teamRef string) (*models.Team, error) + + // ListMyJoined returns all teams the authenticated user has joined. + ListMyJoined(ctx context.Context) ([]*models.Team, error) + + // CreateViaGroup creates a new team associated with a Microsoft 365 group. + CreateViaGroup(ctx context.Context, displayName, mailNickname, visibility string) (*models.Team, error) + + // CreateFromTemplate creates a new team from a template. + CreateFromTemplate(ctx context.Context, displayName, description string, owners, members []string, visibility string, includeMe bool) (string, error) + + // Archive archives a team, optionally making SharePoint read-only for members. + Archive(ctx context.Context, teamRef string, spoReadOnlyForMembers *bool) error + + // Unarchive restores an archived team. + Unarchive(ctx context.Context, teamRef string) error + + // Delete removes a team. + Delete(ctx context.Context, teamRef string) error + + // RestoreDeleted restores a deleted team using the deleted group ID. + RestoreDeleted(ctx context.Context, deletedGroupID string) (string, error) + + // ListMembers returns all members of a team. + ListMembers(ctx context.Context, teamRef string) ([]*models.Member, error) + + // GetMember retrieves a specific member of a team by their member ID or user email. + GetMember(ctx context.Context, teamRef, userRef string) (*models.Member, error) + + // AddMember adds a new member to a team. + AddMember(ctx context.Context, teamRef string, userRef string, isOwner bool) (*models.Member, error) + + // RemoveMember removes a member from a team by their member ID or user email. + RemoveMember(ctx context.Context, teamRef, userRef string) error + + // UpdateMemberRoles updates the roles of a team member (e.g., promote to owner or demote to member). + UpdateMemberRoles(ctx context.Context, teamRef, userRef string, isOwner bool) (*models.Member, error) + + // UpdateTeam applies updates to a team using the provided TeamUpdate object. + UpdateTeam(ctx context.Context, teamRef string, update *models.TeamUpdate) (*models.Team, error) +} +``` + + +### func [NewService]() + +```go +func NewService(teamOps teamsOps, tr resolver.TeamResolver) Service +``` + +NewService creates a new Service instance. + +Generated by [gomarkdoc]() diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..358b7846 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,37 @@ +site_name: Teams API library +site_description: A Go library for interacting with Microsoft Teams API. +site_url: https://pzsp-teams.github.io/lib/ +repo_url: https://github.com/pzsp-teams/lib + +theme: + name: material + features: + - content.code.copy + - navigation.expand + palette: + - scheme: default + primary: teal + accent: teal + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - scheme: slate + primary: teal + accent: teal + toggle: + icon: material/brightness-4 + name: Switch to light mode + +plugins: + - search + +nav: + - Home: index.md + - API: + - Client: client.md + - Teams: teams.md + - Channels: channels.md + - Chats: chats.md + - Models: models.md + - Config: config.md + - Search: search.md diff --git a/search/search.go b/search/search.go index 8001cf73..e69c70fb 100644 --- a/search/search.go +++ b/search/search.go @@ -1,3 +1,17 @@ +// Package search provides types used to build message-search queries and to represent +// paginated search results returned from the messaging backend (e.g., Microsoft Graph). +// +// The package defines: +// - query options (SearchMessagesOptions), +// - pagination controls (SearchPage), +// - predefined time windows (TimeInterval), +// - result containers (SearchResult, SearchResults), +// - and a small concurrency configuration (SearchConfig). +// +// Notes: +// - If Interval is provided, it takes precedence over StartTime and EndTime. +// - "To" / "NotTo" filters typically work for chats, not for team channels. +// - Some providers may not support all filters (e.g., IsRead), depending on API limitations. package search import ( @@ -6,102 +20,110 @@ import ( "github.com/pzsp-teams/lib/models" ) -// SearchPage contains pagination options for searching messages. -// Fields: -// - From: The starting index of the search results. -// - Size: The number of results to return. +// SearchPage defines pagination options for message searches. // -// Note: If not set, default pagination values will be used. From is zero-based. -// Default Size is typically 25 +// From is a zero-based index of the first item to return. +// Size is the maximum number of items to return. +// +// If nil, provider defaults are used (commonly Size=25). type SearchPage struct { From *int32 Size *int32 } -// TimeInterval represents a predefined time interval for message searches. +// TimeInterval is a predefined, human-friendly time window for searching messages. // -// Possible values include: -// - Today -// - Yesterday -// - ThisWeek -// - ThisMonth -// - LastMonth -// - ThisYear -// - LastYear +// When Interval is used in SearchMessagesOptions, it overrides StartTime/EndTime. type TimeInterval string const ( - Today TimeInterval = "today" + // Today selects messages sent today. + Today TimeInterval = "today" + // Yesterday selects messages sent yesterday. Yesterday TimeInterval = "yesterday" - ThisWeek TimeInterval = "this week" + // ThisWeek selects messages sent in the current week. + ThisWeek TimeInterval = "this week" + // ThisMonth selects messages sent in the current month. ThisMonth TimeInterval = "this month" + // LastMonth selects messages sent in the previous month. LastMonth TimeInterval = "last month" - ThisYear TimeInterval = "this year" - LastYear TimeInterval = "last year" + // ThisYear selects messages sent in the current year. + ThisYear TimeInterval = "this year" + // LastYear selects messages sent in the previous year. + LastYear TimeInterval = "last year" ) -// SearchMessagesOptions contains options for searching messages. -// -// Fields: -// - Query: The search query string. -// - SearchPage: Pagination options. -// - From: List of sender email addresses to include. -// - NotFrom: List of sender email addresses to exclude. -// - IsRead: Filter by read status (true for read, false for unread). -// - IsMentioned: Filter by mention status (true for mentioned, false for not mentioned). -// - To: List of recipient email addresses to include. -// - NotTo: List of recipient email addresses to exclude. -// - StartTime: Start time for the sent time range filter. -// - EndTime: End time for the sent time range filter. -// - Interval: Predefined time interval for the sent time filter. -// - NotFromMe: Exclude messages sent by the current user. -// - NotToMe: Exclude messages sent to the current user. -// - FromMe: Include only messages sent by the current user. -// - ToMe: Include only messages sent to the current user. +// SearchMessagesOptions describes filters and parameters used to search for messages. // -// Note: If Interval is set, it takes precedence over StartTime and EndTime. +// Interval takes precedence over StartTime and EndTime. // -// Note: Using `to` clauses works only in chats, not in team channels. -// -// Note: Currently, the queries for IsRead may not function as expected due to API limitations. +// Provider notes: +// - "To" / "NotTo" filters typically work only for chats (not for team channels). +// - Some providers may not support all filters (e.g., IsRead) due to API limitations. type SearchMessagesOptions struct { - Query *string - SearchPage *SearchPage - From []string - NotFrom []string - IsRead *bool + // Query is the full-text query string (provider-dependent syntax). + Query *string + + // SearchPage controls pagination (From/Size). + SearchPage *SearchPage + + // From includes messages from these sender email addresses. + From []string + // NotFrom excludes messages from these sender email addresses. + NotFrom []string + + // IsRead filters by read status (true=read, false=unread). + // Note: may be ignored by some providers due to API limitations. + IsRead *bool + + // IsMentioned filters by whether the current user is mentioned. IsMentioned *bool - To []string - NotTo []string - StartTime *time.Time - EndTime *time.Time - Interval *TimeInterval - NotFromMe bool - NotToMe bool - FromMe bool - ToMe bool + + // To includes messages addressed to these recipients. + // Note: commonly chat-only; may not work for channel messages. + To []string + // NotTo excludes messages addressed to these recipients. + // Note: commonly chat-only; may not work for channel messages. + NotTo []string + + // StartTime is the inclusive start of the sent-time filter. + StartTime *time.Time + // EndTime is the exclusive end of the sent-time filter (provider-dependent). + EndTime *time.Time + + // Interval is a predefined time window; when set it overrides StartTime/EndTime. + Interval *TimeInterval + + // NotFromMe excludes messages sent by the current user. + NotFromMe bool + // NotToMe excludes messages sent to the current user (provider-dependent). + NotToMe bool + // FromMe includes only messages sent by the current user. + FromMe bool + // ToMe includes only messages sent to the current user (provider-dependent). + ToMe bool } -// SearchResult represents a single search result containing a message and its context. +// SearchResult is a single search hit together with its location context. // -// Fields: -// - Message: The chat message. -// - ChannelID: The ID of the channel where the message was found (if applicable). -// - TeamID: The ID of the team where the message was found (if applicable). -// - ChatID: The ID of the chat where the message was found (if applicable). +// Exactly which of ChannelID/TeamID/ChatID is set depends on where the message was found. type SearchResult struct { - Message *models.Message + // Message is the found message payload. + Message *models.Message + + // ChannelID is set when the message was found in a channel. ChannelID *string - TeamID *string - ChatID *string + // TeamID is set when the message was found in a team (typically with ChannelID). + TeamID *string + // ChatID is set when the message was found in a chat. + ChatID *string } -// SearchResults represents the results of a message search. -// -// Fields: -// - Messages: A list of search results. -// - NextFrom: The pagination token for the next page of results (if applicable). +// SearchResults is a paginated container of search hits. type SearchResults struct { + // Messages contains the list of hits for this page. Messages []*SearchResult + + // NextFrom is the pagination cursor/index to continue from (if available). NextFrom *int32 } diff --git a/search/search_config.go b/search/search_config.go index c9f1451f..749adf0d 100644 --- a/search/search_config.go +++ b/search/search_config.go @@ -1,15 +1,15 @@ package search -// SearchConfig holds configuration for search operations. +// SearchConfig configures how search operations are executed. // -// MaxWorkers specifies the maximum number of concurrent workers to use -// when fetching search results. A higher number can speed up searches but -// may increase resource usage. Default is 8. +// MaxWorkers defines the maximum number of concurrent workers used to fetch +// and enrich search results. Higher values can speed up searches but increase +// resource usage and pressure on the upstream API. type SearchConfig struct { MaxWorkers int } -// DefaultSearchConfig returns a SearchConfig with default settings. +// DefaultSearchConfig returns a SearchConfig initialized with sane defaults. func DefaultSearchConfig() *SearchConfig { return &SearchConfig{ MaxWorkers: 8,