diff --git a/frontend/apps/site/client.ts b/frontend/apps/site/client.ts index cacb2af7a3..2592404e19 100644 --- a/frontend/apps/site/client.ts +++ b/frontend/apps/site/client.ts @@ -16,6 +16,7 @@ import { Daemon, Networking, Groups, + Website, } from '@mintter/shared' const loggingInterceptor: Interceptor = (next) => async (req) => { @@ -82,3 +83,4 @@ export const accountsClient = createPromiseClient(Accounts, transport) export const groupsClient = createPromiseClient(Groups, transport) export const daemonClient = createPromiseClient(Daemon, transport) export const networkingClient = createPromiseClient(Networking, transport) +export const websiteClient = createPromiseClient(Website, transport) diff --git a/frontend/apps/site/pages/[pathName].tsx b/frontend/apps/site/pages/[pathName].tsx index 7681724d16..91ba651c0e 100644 --- a/frontend/apps/site/pages/[pathName].tsx +++ b/frontend/apps/site/pages/[pathName].tsx @@ -12,9 +12,11 @@ export const getServerSideProps: GetServerSideProps< EveryPageProps & PubSlugPageProps > = async (context) => { const pathName = (context.params?.pathName as string) || '' - const {groupEid} = await getSiteGroup() + const {groupEid, version} = await getSiteGroup() + if (!groupEid) return {notFound: true} return await getGroupPathNamePageProps({ groupEid, + version, pathName, context, }) diff --git a/frontend/apps/site/pages/index.tsx b/frontend/apps/site/pages/index.tsx index c53791dcb6..0b9c8c452b 100644 --- a/frontend/apps/site/pages/index.tsx +++ b/frontend/apps/site/pages/index.tsx @@ -12,8 +12,8 @@ export default function HomePage(props: GroupPageProps) { export const getServerSideProps: GetServerSideProps< EveryPageProps & PubSlugPageProps > = async (context) => { - const {groupEid} = await getSiteGroup() - console.log('serer side', context.query) + const {groupEid, version} = await getSiteGroup() + if (!groupEid) return {notFound: true} const view = getGroupView(context.query.view) - return await getGroupPageProps({groupEid, context, view}) + return await getGroupPageProps({groupEid, version, context, view}) } diff --git a/frontend/apps/site/server/group.ts b/frontend/apps/site/server/group.ts index 1f5245d7ed..a043f8ebc5 100644 --- a/frontend/apps/site/server/group.ts +++ b/frontend/apps/site/server/group.ts @@ -14,10 +14,12 @@ export function getGroupView(input: string | string[] | undefined): GroupView { export async function getGroupPathNamePageProps({ groupEid, + version, pathName, context, }: { groupEid: string + version: string | undefined pathName: string context: GetServerSidePropsContext }) { @@ -34,8 +36,13 @@ export async function getGroupPathNamePageProps({ 'x-mintter-site-p2p-addresses', peerInfo.addrs.join(','), ) - const group = await helpers.group.get.fetch({groupId}) - const groupContent = await helpers.group.listContent.fetch({groupId}) + const {query} = context + const groupVersion = query.v ? String(query.v) : version + const group = await helpers.group.get.fetch({groupId, version: groupVersion}) + const groupContent = await helpers.group.listContent.fetch({ + groupId, + version: groupVersion, + }) return { props: await getPageProps(helpers, {pathName, groupId}), @@ -44,17 +51,19 @@ export async function getGroupPathNamePageProps({ export async function getGroupPageProps({ groupEid, + version, context, view, }: { groupEid: string + version: string | undefined context: GetServerSidePropsContext view: GroupView }) { const {params, query} = context const groupId = groupEid ? createHmId('g', groupEid) : undefined - let version = query.v ? String(query.v) : null + const groupVersion = query.v ? String(query.v) : version setAllowAnyHostGetCORS(context.res) @@ -64,9 +73,11 @@ export async function getGroupPageProps({ const groupRecord = await helpers.group.get.fetch({ groupId, + version: groupVersion, }) const members = await helpers.group.listMembers.fetch({ groupId, + version: groupVersion, }) await Promise.all( members.map((member) => diff --git a/frontend/apps/site/server/routers/_app.ts b/frontend/apps/site/server/routers/_app.ts index 6908fc0777..712d3345a7 100644 --- a/frontend/apps/site/server/routers/_app.ts +++ b/frontend/apps/site/server/routers/_app.ts @@ -246,11 +246,13 @@ const groupRouter = router({ .input( z.object({ groupId: z.string(), + version: z.string().optional(), }), ) .query(async ({input}) => { const list = await groupsClient.listContent({ id: input.groupId, + version: input.version, }) const listedDocs = await Promise.all( Object.entries(list.content).map(async ([pathName, pubUrl]) => { @@ -281,11 +283,13 @@ const groupRouter = router({ .input( z.object({ groupId: z.string(), + version: z.string().optional(), }), ) .query(async ({input}) => { const list = await groupsClient.listMembers({ id: input.groupId, + version: input.version, }) return Object.entries(list.members || {}).map(([account, role]) => ({ account, diff --git a/frontend/apps/site/server/site-info.ts b/frontend/apps/site/server/site-info.ts index cf7886d0f3..b927044aa8 100644 --- a/frontend/apps/site/server/site-info.ts +++ b/frontend/apps/site/server/site-info.ts @@ -1,13 +1,12 @@ -// const gatewayHostWithProtocol = process.env.HM_BASE_URL -// const gatewayHost = new URL(gatewayHostWithProtocol || '').hostname +import {websiteClient} from '../client' export async function getSiteGroup(): Promise<{ - groupEid: string + groupEid?: string version?: string | null }> { - // todo, query for site group + const siteInfo = await websiteClient.getSiteInfo({}) return { - groupEid: '8ctYvUJwv9kmpjCT4RjBeD', - version: null, + groupEid: siteInfo.groupId || undefined, + version: siteInfo.groupVersion || null, } } diff --git a/frontend/packages/shared/src/client/.generated/groups/v1alpha/website_connect.ts b/frontend/packages/shared/src/client/.generated/groups/v1alpha/website_connect.ts new file mode 100644 index 0000000000..ef4aee7b5b --- /dev/null +++ b/frontend/packages/shared/src/client/.generated/groups/v1alpha/website_connect.ts @@ -0,0 +1,54 @@ +// @generated by protoc-gen-connect-es v0.13.0 with parameter "target=ts,import_extension=none" +// @generated from file groups/v1alpha/website.proto (package com.mintter.groups.v1alpha, syntax proto3) +/* eslint-disable */ +// @ts-nocheck + +import { GetSiteInfoRequest, InitializeServerRequest, InitializeServerResponse, PublicSiteInfo, PublishBlobsRequest, PublishBlobsResponse } from "./website_pb"; +import { MethodKind } from "@bufbuild/protobuf"; + +/** + * API service exposed by the website server. + * It's exposed as gRPC over Libp2p. + * + * @generated from service com.mintter.groups.v1alpha.Website + */ +export const Website = { + typeName: "com.mintter.groups.v1alpha.Website", + methods: { + /** + * Gets the public information about the website. + * This information is also available as JSON over HTTP on `/.well-known/hypermedia-site`. + * + * @generated from rpc com.mintter.groups.v1alpha.Website.GetSiteInfo + */ + getSiteInfo: { + name: "GetSiteInfo", + I: GetSiteInfoRequest, + O: PublicSiteInfo, + kind: MethodKind.Unary, + }, + /** + * Initializes the server to become a website for a specific group. + * + * @generated from rpc com.mintter.groups.v1alpha.Website.InitializeServer + */ + initializeServer: { + name: "InitializeServer", + I: InitializeServerRequest, + O: InitializeServerResponse, + kind: MethodKind.Unary, + }, + /** + * Publishes blobs to the website. + * + * @generated from rpc com.mintter.groups.v1alpha.Website.PublishBlobs + */ + publishBlobs: { + name: "PublishBlobs", + I: PublishBlobsRequest, + O: PublishBlobsResponse, + kind: MethodKind.Unary, + }, + } +} as const; + diff --git a/frontend/packages/shared/src/client/.generated/groups/v1alpha/website_pb.ts b/frontend/packages/shared/src/client/.generated/groups/v1alpha/website_pb.ts new file mode 100644 index 0000000000..41864b6c5b --- /dev/null +++ b/frontend/packages/shared/src/client/.generated/groups/v1alpha/website_pb.ts @@ -0,0 +1,316 @@ +// @generated by protoc-gen-es v1.3.1 with parameter "target=ts,import_extension=none" +// @generated from file groups/v1alpha/website.proto (package com.mintter.groups.v1alpha, syntax proto3) +/* eslint-disable */ +// @ts-nocheck + +import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf"; +import { Message, proto3 } from "@bufbuild/protobuf"; + +/** + * Request for getting the public site information. + * + * @generated from message com.mintter.groups.v1alpha.GetSiteInfoRequest + */ +export class GetSiteInfoRequest extends Message { + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "com.mintter.groups.v1alpha.GetSiteInfoRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): GetSiteInfoRequest { + return new GetSiteInfoRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): GetSiteInfoRequest { + return new GetSiteInfoRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): GetSiteInfoRequest { + return new GetSiteInfoRequest().fromJsonString(jsonString, options); + } + + static equals(a: GetSiteInfoRequest | PlainMessage | undefined, b: GetSiteInfoRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(GetSiteInfoRequest, a, b); + } +} + +/** + * Request for initializing the site. + * + * @generated from message com.mintter.groups.v1alpha.InitializeServerRequest + */ +export class InitializeServerRequest extends Message { + /** + * Required. The secret provided during the site deployment process. + * It's a trust-on-first-use, one-time-use secret that is used for the initial site setup, + * during which the site remembers the groups that it must serve, and who is the owner of the site. + * + * @generated from field: string secret = 1; + */ + secret = ""; + + /** + * Required. ID of the group that should be served on this site. + * + * @generated from field: string group_id = 2; + */ + groupId = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "com.mintter.groups.v1alpha.InitializeServerRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "secret", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "group_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): InitializeServerRequest { + return new InitializeServerRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): InitializeServerRequest { + return new InitializeServerRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): InitializeServerRequest { + return new InitializeServerRequest().fromJsonString(jsonString, options); + } + + static equals(a: InitializeServerRequest | PlainMessage | undefined, b: InitializeServerRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(InitializeServerRequest, a, b); + } +} + +/** + * Response for initializing the site. + * + * @generated from message com.mintter.groups.v1alpha.InitializeServerResponse + */ +export class InitializeServerResponse extends Message { + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "com.mintter.groups.v1alpha.InitializeServerResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): InitializeServerResponse { + return new InitializeServerResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): InitializeServerResponse { + return new InitializeServerResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): InitializeServerResponse { + return new InitializeServerResponse().fromJsonString(jsonString, options); + } + + static equals(a: InitializeServerResponse | PlainMessage | undefined, b: InitializeServerResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(InitializeServerResponse, a, b); + } +} + +/** + * Request for publishing blobs. + * + * @generated from message com.mintter.groups.v1alpha.PublishBlobsRequest + */ +export class PublishBlobsRequest extends Message { + /** + * List of blob CIDs that we expect to be available on the site. + * + * @generated from field: repeated string blobs = 1; + */ + blobs: string[] = []; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "com.mintter.groups.v1alpha.PublishBlobsRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "blobs", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): PublishBlobsRequest { + return new PublishBlobsRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): PublishBlobsRequest { + return new PublishBlobsRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): PublishBlobsRequest { + return new PublishBlobsRequest().fromJsonString(jsonString, options); + } + + static equals(a: PublishBlobsRequest | PlainMessage | undefined, b: PublishBlobsRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(PublishBlobsRequest, a, b); + } +} + +/** + * Response for publishing blobs. + * + * @generated from message com.mintter.groups.v1alpha.PublishBlobsResponse + */ +export class PublishBlobsResponse extends Message { + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "com.mintter.groups.v1alpha.PublishBlobsResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): PublishBlobsResponse { + return new PublishBlobsResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): PublishBlobsResponse { + return new PublishBlobsResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): PublishBlobsResponse { + return new PublishBlobsResponse().fromJsonString(jsonString, options); + } + + static equals(a: PublishBlobsResponse | PlainMessage | undefined, b: PublishBlobsResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(PublishBlobsResponse, a, b); + } +} + +/** + * Publicly available information about the website. + * + * @generated from message com.mintter.groups.v1alpha.PublicSiteInfo + */ +export class PublicSiteInfo extends Message { + /** + * P2P information for the website. + * + * @generated from field: com.mintter.groups.v1alpha.PeerInfo peer_info = 1; + */ + peerInfo?: PeerInfo; + + /** + * Group ID being served on the site. + * Can be empty if site is not initialized yet. + * + * @generated from field: string group_id = 2; + */ + groupId = ""; + + /** + * Version of the group according to the website server. + * + * @generated from field: string group_version = 3; + */ + groupVersion = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "com.mintter.groups.v1alpha.PublicSiteInfo"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "peer_info", kind: "message", T: PeerInfo }, + { no: 2, name: "group_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 3, name: "group_version", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): PublicSiteInfo { + return new PublicSiteInfo().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): PublicSiteInfo { + return new PublicSiteInfo().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): PublicSiteInfo { + return new PublicSiteInfo().fromJsonString(jsonString, options); + } + + static equals(a: PublicSiteInfo | PlainMessage | undefined, b: PublicSiteInfo | PlainMessage | undefined): boolean { + return proto3.util.equals(PublicSiteInfo, a, b); + } +} + +/** + * Peer information for P2P network. + * + * @generated from message com.mintter.groups.v1alpha.PeerInfo + */ +export class PeerInfo extends Message { + /** + * Libp2p peer ID. + * + * @generated from field: string peer_id = 1; + */ + peerId = ""; + + /** + * Multiaddrs for the peer, + * without the peer ID, + * in order to use it with libp2p AddrInfo API. + * + * @generated from field: repeated string addrs = 2; + */ + addrs: string[] = []; + + /** + * Mintter Account ID of the site. + * + * @generated from field: string account_id = 3; + */ + accountId = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "com.mintter.groups.v1alpha.PeerInfo"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "peer_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "addrs", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true }, + { no: 3, name: "account_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): PeerInfo { + return new PeerInfo().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): PeerInfo { + return new PeerInfo().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): PeerInfo { + return new PeerInfo().fromJsonString(jsonString, options); + } + + static equals(a: PeerInfo | PlainMessage | undefined, b: PeerInfo | PlainMessage | undefined): boolean { + return proto3.util.equals(PeerInfo, a, b); + } +} + diff --git a/frontend/packages/shared/src/client/index.ts b/frontend/packages/shared/src/client/index.ts index 5e93573082..02de8820e8 100644 --- a/frontend/packages/shared/src/client/index.ts +++ b/frontend/packages/shared/src/client/index.ts @@ -38,6 +38,8 @@ export { Role, ListGroupsRequest, } from './.generated/groups/v1alpha/groups_pb' +export * from './.generated/groups/v1alpha/website_pb' +export * from './.generated/groups/v1alpha/website_connect' export { Change, DiscoverEntityRequest, diff --git a/proto/groups/v1alpha/BUILD.plz b/proto/groups/v1alpha/BUILD.plz index cc000e5a49..e8078ca636 100644 --- a/proto/groups/v1alpha/BUILD.plz +++ b/proto/groups/v1alpha/BUILD.plz @@ -1,11 +1,9 @@ subinclude("//build/rules/mintter:defs") -mtt_go_proto( - name = "go", +mtt_proto_codegen( srcs = glob(["*.proto"]), -) - -mtt_js_proto( - name = "js", - srcs = ["groups.proto"], + languages = [ + "go", + "js", + ], ) diff --git a/proto/groups/v1alpha/js.gensum b/proto/groups/v1alpha/js.gensum index 0e905e65d7..26292bcace 100644 --- a/proto/groups/v1alpha/js.gensum +++ b/proto/groups/v1alpha/js.gensum @@ -1,2 +1,2 @@ -srcs: ed6cf9cc25b404e13a2f57906f7cdb2f -outs: 9253b7914c1765262c5b08a63675e2af +srcs: 7db115769d3a5fe0b820c83f6256f20c +outs: de12a2602c16cba6fef17e1d40970b94