From 50c8d18df92183656ebc83b4f8f94cce4fcb9d21 Mon Sep 17 00:00:00 2001 From: trev Date: Fri, 14 Mar 2025 14:14:35 -0400 Subject: [PATCH] feat: items page --- client/src/app.css | 2 + client/src/lib/services/item/v1/item_pb.ts | 296 +++++++ client/src/lib/transport.ts | 7 +- client/src/lib/ui/Modal.svelte | 51 ++ client/src/routes/(app)/+layout.svelte | 2 +- client/src/routes/(app)/items/+page.svelte | 332 ++++++++ client/static/openapi/openapi.yaml | 464 ++++++++++- flake.nix | 5 +- proto/item/v1/item.proto | 60 ++ server/internal/database/migrate.go | 2 +- server/internal/handlers/item.go | 171 ++++ server/internal/interceptors/auth.go | 7 +- server/internal/interceptors/ratelimit.go | 22 +- server/internal/models/item.go | 33 + server/internal/services/item/v1/item.pb.go | 759 ++++++++++++++++++ .../item/v1/itemv1connect/item.connect.go | 220 +++++ server/main.go | 5 +- 17 files changed, 2384 insertions(+), 54 deletions(-) create mode 100644 client/src/lib/services/item/v1/item_pb.ts create mode 100644 client/src/lib/ui/Modal.svelte create mode 100644 client/src/routes/(app)/items/+page.svelte create mode 100644 proto/item/v1/item.proto create mode 100644 server/internal/handlers/item.go create mode 100644 server/internal/models/item.go create mode 100644 server/internal/services/item/v1/item.pb.go create mode 100644 server/internal/services/item/v1/itemv1connect/item.connect.go diff --git a/client/src/app.css b/client/src/app.css index fbeef3f..2e329c7 100644 --- a/client/src/app.css +++ b/client/src/app.css @@ -18,5 +18,7 @@ --color-subtext-1: #bac2de; --color-text: #cdd6f4; + --color-sky: #89dceb; + --color-red: #f38ba8; } \ No newline at end of file diff --git a/client/src/lib/services/item/v1/item_pb.ts b/client/src/lib/services/item/v1/item_pb.ts new file mode 100644 index 0000000..9fe15c9 --- /dev/null +++ b/client/src/lib/services/item/v1/item_pb.ts @@ -0,0 +1,296 @@ +// @generated by protoc-gen-es v2.2.3 with parameter "target=ts" +// @generated from file item/v1/item.proto (package item.v1, syntax proto3) +/* eslint-disable */ + +import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv1"; +import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv1"; +import type { Timestamp } from "@bufbuild/protobuf/wkt"; +import { file_google_protobuf_timestamp } from "@bufbuild/protobuf/wkt"; +import type { Message } from "@bufbuild/protobuf"; + +/** + * Describes the file item/v1/item.proto. + */ +export const file_item_v1_item: GenFile = /*@__PURE__*/ + fileDesc("ChJpdGVtL3YxL2l0ZW0ucHJvdG8SB2l0ZW0udjEinAEKBEl0ZW0SDwoCaWQYASABKA1IAIgBARIMCgRuYW1lGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEg0KBXByaWNlGAQgASgCEhAKCHF1YW50aXR5GAUgASgNEi4KBWFkZGVkGAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgBiAEBQgUKA19pZEIICgZfYWRkZWQiHAoOR2V0SXRlbVJlcXVlc3QSCgoCaWQYASABKA0iLgoPR2V0SXRlbVJlc3BvbnNlEhsKBGl0ZW0YASABKAsyDS5pdGVtLnYxLkl0ZW0i3wEKD0dldEl0ZW1zUmVxdWVzdBIuCgVzdGFydBgBIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBIAIgBARIsCgNlbmQYAiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wSAGIAQESEwoGZmlsdGVyGAMgASgJSAKIAQESEgoFbGltaXQYBCABKA1IA4gBARITCgZvZmZzZXQYBSABKA1IBIgBAUIICgZfc3RhcnRCBgoEX2VuZEIJCgdfZmlsdGVyQggKBl9saW1pdEIJCgdfb2Zmc2V0Ij8KEEdldEl0ZW1zUmVzcG9uc2USHAoFaXRlbXMYASADKAsyDS5pdGVtLnYxLkl0ZW0SDQoFY291bnQYAiABKAQiMAoRQ3JlYXRlSXRlbVJlcXVlc3QSGwoEaXRlbRgBIAEoCzINLml0ZW0udjEuSXRlbSIxChJDcmVhdGVJdGVtUmVzcG9uc2USGwoEaXRlbRgBIAEoCzINLml0ZW0udjEuSXRlbSIwChFVcGRhdGVJdGVtUmVxdWVzdBIbCgRpdGVtGAEgASgLMg0uaXRlbS52MS5JdGVtIjEKElVwZGF0ZUl0ZW1SZXNwb25zZRIbCgRpdGVtGAEgASgLMg0uaXRlbS52MS5JdGVtIh8KEURlbGV0ZUl0ZW1SZXF1ZXN0EgoKAmlkGAEgASgNIhQKEkRlbGV0ZUl0ZW1SZXNwb25zZTLrAgoLSXRlbVNlcnZpY2USPgoHR2V0SXRlbRIXLml0ZW0udjEuR2V0SXRlbVJlcXVlc3QaGC5pdGVtLnYxLkdldEl0ZW1SZXNwb25zZSIAEkEKCEdldEl0ZW1zEhguaXRlbS52MS5HZXRJdGVtc1JlcXVlc3QaGS5pdGVtLnYxLkdldEl0ZW1zUmVzcG9uc2UiABJHCgpDcmVhdGVJdGVtEhouaXRlbS52MS5DcmVhdGVJdGVtUmVxdWVzdBobLml0ZW0udjEuQ3JlYXRlSXRlbVJlc3BvbnNlIgASRwoKVXBkYXRlSXRlbRIaLml0ZW0udjEuVXBkYXRlSXRlbVJlcXVlc3QaGy5pdGVtLnYxLlVwZGF0ZUl0ZW1SZXNwb25zZSIAEkcKCkRlbGV0ZUl0ZW0SGi5pdGVtLnYxLkRlbGV0ZUl0ZW1SZXF1ZXN0GhsuaXRlbS52MS5EZWxldGVJdGVtUmVzcG9uc2UiAEKdAQoLY29tLml0ZW0udjFCCUl0ZW1Qcm90b1ABWkZnaXRodWIuY29tL3Nwb3RkZW1vNC90cmV2c3RhY2svc2VydmVyL2ludGVybmFsL3NlcnZpY2VzL2l0ZW0vdjE7aXRlbXYxogIDSVhYqgIHSXRlbS5WMcoCB0l0ZW1cVjHiAhNJdGVtXFYxXEdQQk1ldGFkYXRh6gIISXRlbTo6VjFiBnByb3RvMw", [file_google_protobuf_timestamp]); + +/** + * @generated from message item.v1.Item + */ +export type Item = Message<"item.v1.Item"> & { + /** + * @generated from field: optional uint32 id = 1; + */ + id?: number; + + /** + * @generated from field: string name = 2; + */ + name: string; + + /** + * @generated from field: string description = 3; + */ + description: string; + + /** + * @generated from field: float price = 4; + */ + price: number; + + /** + * @generated from field: uint32 quantity = 5; + */ + quantity: number; + + /** + * @generated from field: optional google.protobuf.Timestamp added = 6; + */ + added?: Timestamp; +}; + +/** + * Describes the message item.v1.Item. + * Use `create(ItemSchema)` to create a new message. + */ +export const ItemSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_item_v1_item, 0); + +/** + * @generated from message item.v1.GetItemRequest + */ +export type GetItemRequest = Message<"item.v1.GetItemRequest"> & { + /** + * @generated from field: uint32 id = 1; + */ + id: number; +}; + +/** + * Describes the message item.v1.GetItemRequest. + * Use `create(GetItemRequestSchema)` to create a new message. + */ +export const GetItemRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_item_v1_item, 1); + +/** + * @generated from message item.v1.GetItemResponse + */ +export type GetItemResponse = Message<"item.v1.GetItemResponse"> & { + /** + * @generated from field: item.v1.Item item = 1; + */ + item?: Item; +}; + +/** + * Describes the message item.v1.GetItemResponse. + * Use `create(GetItemResponseSchema)` to create a new message. + */ +export const GetItemResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_item_v1_item, 2); + +/** + * @generated from message item.v1.GetItemsRequest + */ +export type GetItemsRequest = Message<"item.v1.GetItemsRequest"> & { + /** + * @generated from field: optional google.protobuf.Timestamp start = 1; + */ + start?: Timestamp; + + /** + * @generated from field: optional google.protobuf.Timestamp end = 2; + */ + end?: Timestamp; + + /** + * @generated from field: optional string filter = 3; + */ + filter?: string; + + /** + * @generated from field: optional uint32 limit = 4; + */ + limit?: number; + + /** + * @generated from field: optional uint32 offset = 5; + */ + offset?: number; +}; + +/** + * Describes the message item.v1.GetItemsRequest. + * Use `create(GetItemsRequestSchema)` to create a new message. + */ +export const GetItemsRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_item_v1_item, 3); + +/** + * @generated from message item.v1.GetItemsResponse + */ +export type GetItemsResponse = Message<"item.v1.GetItemsResponse"> & { + /** + * @generated from field: repeated item.v1.Item items = 1; + */ + items: Item[]; + + /** + * @generated from field: uint64 count = 2; + */ + count: bigint; +}; + +/** + * Describes the message item.v1.GetItemsResponse. + * Use `create(GetItemsResponseSchema)` to create a new message. + */ +export const GetItemsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_item_v1_item, 4); + +/** + * @generated from message item.v1.CreateItemRequest + */ +export type CreateItemRequest = Message<"item.v1.CreateItemRequest"> & { + /** + * @generated from field: item.v1.Item item = 1; + */ + item?: Item; +}; + +/** + * Describes the message item.v1.CreateItemRequest. + * Use `create(CreateItemRequestSchema)` to create a new message. + */ +export const CreateItemRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_item_v1_item, 5); + +/** + * @generated from message item.v1.CreateItemResponse + */ +export type CreateItemResponse = Message<"item.v1.CreateItemResponse"> & { + /** + * @generated from field: item.v1.Item item = 1; + */ + item?: Item; +}; + +/** + * Describes the message item.v1.CreateItemResponse. + * Use `create(CreateItemResponseSchema)` to create a new message. + */ +export const CreateItemResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_item_v1_item, 6); + +/** + * @generated from message item.v1.UpdateItemRequest + */ +export type UpdateItemRequest = Message<"item.v1.UpdateItemRequest"> & { + /** + * @generated from field: item.v1.Item item = 1; + */ + item?: Item; +}; + +/** + * Describes the message item.v1.UpdateItemRequest. + * Use `create(UpdateItemRequestSchema)` to create a new message. + */ +export const UpdateItemRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_item_v1_item, 7); + +/** + * @generated from message item.v1.UpdateItemResponse + */ +export type UpdateItemResponse = Message<"item.v1.UpdateItemResponse"> & { + /** + * @generated from field: item.v1.Item item = 1; + */ + item?: Item; +}; + +/** + * Describes the message item.v1.UpdateItemResponse. + * Use `create(UpdateItemResponseSchema)` to create a new message. + */ +export const UpdateItemResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_item_v1_item, 8); + +/** + * @generated from message item.v1.DeleteItemRequest + */ +export type DeleteItemRequest = Message<"item.v1.DeleteItemRequest"> & { + /** + * @generated from field: uint32 id = 1; + */ + id: number; +}; + +/** + * Describes the message item.v1.DeleteItemRequest. + * Use `create(DeleteItemRequestSchema)` to create a new message. + */ +export const DeleteItemRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_item_v1_item, 9); + +/** + * @generated from message item.v1.DeleteItemResponse + */ +export type DeleteItemResponse = Message<"item.v1.DeleteItemResponse"> & { +}; + +/** + * Describes the message item.v1.DeleteItemResponse. + * Use `create(DeleteItemResponseSchema)` to create a new message. + */ +export const DeleteItemResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_item_v1_item, 10); + +/** + * @generated from service item.v1.ItemService + */ +export const ItemService: GenService<{ + /** + * @generated from rpc item.v1.ItemService.GetItem + */ + getItem: { + methodKind: "unary"; + input: typeof GetItemRequestSchema; + output: typeof GetItemResponseSchema; + }, + /** + * @generated from rpc item.v1.ItemService.GetItems + */ + getItems: { + methodKind: "unary"; + input: typeof GetItemsRequestSchema; + output: typeof GetItemsResponseSchema; + }, + /** + * @generated from rpc item.v1.ItemService.CreateItem + */ + createItem: { + methodKind: "unary"; + input: typeof CreateItemRequestSchema; + output: typeof CreateItemResponseSchema; + }, + /** + * @generated from rpc item.v1.ItemService.UpdateItem + */ + updateItem: { + methodKind: "unary"; + input: typeof UpdateItemRequestSchema; + output: typeof UpdateItemResponseSchema; + }, + /** + * @generated from rpc item.v1.ItemService.DeleteItem + */ + deleteItem: { + methodKind: "unary"; + input: typeof DeleteItemRequestSchema; + output: typeof DeleteItemResponseSchema; + }, +}> = /*@__PURE__*/ + serviceDesc(file_item_v1_item, 0); + diff --git a/client/src/lib/transport.ts b/client/src/lib/transport.ts index 5257e14..7d2aeba 100644 --- a/client/src/lib/transport.ts +++ b/client/src/lib/transport.ts @@ -1,8 +1,8 @@ import { createConnectTransport } from "@connectrpc/connect-web" -import { Code, ConnectError, createClient } from "@connectrpc/connect" +import { Code, ConnectError, createClient, type Interceptor } from "@connectrpc/connect" import { AuthService } from "$lib/services/user/v1/auth_pb"; import { UserService } from "$lib/services/user/v1/user_pb"; -import type { Interceptor } from "@connectrpc/connect"; +import { ItemService } from "$lib/services/item/v1/item_pb"; import { goto } from "$app/navigation"; const redirector: Interceptor = (next) => async (req) => { @@ -23,4 +23,5 @@ const transport = createConnectTransport({ }); export const AuthClient = createClient(AuthService, transport); -export const UserClient = createClient(UserService, transport); \ No newline at end of file +export const UserClient = createClient(UserService, transport); +export const ItemClient = createClient(ItemService, transport); \ No newline at end of file diff --git a/client/src/lib/ui/Modal.svelte b/client/src/lib/ui/Modal.svelte new file mode 100644 index 0000000..0e543c8 --- /dev/null +++ b/client/src/lib/ui/Modal.svelte @@ -0,0 +1,51 @@ + + + + + {@render trigger()} + + + + {#snippet child({ props, open })} + {#if open} +
+
+
+ {/if} + {/snippet} +
+ + {#snippet child({ props, open })} + {#if open} +
+
+ {@render content()} +
+
+ {/if} + {/snippet} +
+
+
diff --git a/client/src/routes/(app)/+layout.svelte b/client/src/routes/(app)/+layout.svelte index bf5c19c..fe8dee2 100644 --- a/client/src/routes/(app)/+layout.svelte +++ b/client/src/routes/(app)/+layout.svelte @@ -34,7 +34,7 @@ }, { name: 'Items', - href: '/items', + href: '/items/', icon: LayoutList }, { diff --git a/client/src/routes/(app)/items/+page.svelte b/client/src/routes/(app)/items/+page.svelte new file mode 100644 index 0000000..739f90c --- /dev/null +++ b/client/src/routes/(app)/items/+page.svelte @@ -0,0 +1,332 @@ + + +
+ + + + + + + + + + + + + {#await items} + + + + + + + + {:then items} + {#each items as item} + + + + + + + + + {/each} + {/await} + +
AddedNameDescriptionPriceQuantity
+ {item.added ? timestampDate(item.added).toLocaleString() : ''} + {item.name}{item.description}{item.price}{item.quantity} +
+ editsOpen.has(item.id!) ? editsOpen.get(item.id!)! : editsOpen.set(item.id!, false) && editsOpen.get(item.id!)!, + (value) => editsOpen.set(item.id!, value) + }> + {#snippet trigger()} + + {/snippet} + + {#snippet content()} +

+ Edit {item.name} +

+
{ + e.preventDefault(); + const form = e.target as HTMLFormElement; + const formData = new FormData(form); + const name = formData.get('name')?.toString(); + const description = formData.get('description')?.toString(); + const price = formData.get('price')?.toString(); + const quantity = formData.get('quantity')?.toString(); + + try { + const response = await ItemClient.updateItem({ + item: { + id: item.id, + name: name, + description: description, + price: parseFloat(price ?? '0'), + quantity: parseInt(quantity ?? '0') + } + }); + + if (response.item && item.id) { + toast.success(`item "${name}" saved`); + editsOpen.set(item.id, false) + await updateItems(); + } + } catch (err) { + const error = ConnectError.from(err); + toast.error(error.rawMessage); + } + }} + > +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + Submit + +
+
+ {/snippet} +
+ + deletesOpen.has(item.id!) ? deletesOpen.get(item.id!)! : deletesOpen.set(item.id!, false) && deletesOpen.get(item.id!)!, + (value) => deletesOpen.set(item.id!, value) + }> + {#snippet trigger()} + + {/snippet} + + {#snippet content()} +

+ Delete {item.name} +

+
{ + e.preventDefault(); + + try { + await ItemClient.deleteItem({ + id: item.id + }); + + toast.success(`item "${item.name}" deleted`); + deletesOpen.set(item.id!, false); + await updateItems(); + } catch (err) { + const error = ConnectError.from(err); + toast.error(error.rawMessage); + } + }} + > +
+ Are you sure you want to delete "{item.name}"? +
+ + Confirm + +
+
+
+ {/snippet} +
+
+
+
+ +
+ + {#snippet trigger()} + + {/snippet} + + {#snippet content()} +

Add Item

+
{ + e.preventDefault(); + const form = e.target as HTMLFormElement; + const formData = new FormData(form); + const name = formData.get('name')?.toString(); + const description = formData.get('description')?.toString(); + const price = formData.get('price')?.toString(); + const quantity = formData.get('quantity')?.toString(); + + try { + const response = await ItemClient.createItem({ + item: { + name: name, + description: description, + price: parseFloat(price ?? '0'), + quantity: parseInt(quantity ?? '0') + } + }); + + if (response.item) { + form.reset(); + toast.success(`item "${name}" added`); + addedOpen = false; + await updateItems(); + } + } catch (err) { + const error = ConnectError.from(err); + toast.error(error.rawMessage); + } + }} + > +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + Submit + +
+
+ {/snippet} +
+
diff --git a/client/static/openapi/openapi.yaml b/client/static/openapi/openapi.yaml index 42180f8..9eef2ad 100644 --- a/client/static/openapi/openapi.yaml +++ b/client/static/openapi/openapi.yaml @@ -15,50 +15,225 @@ components: scheme: bearer bearerFormat: JWT schemas: - user.v1.LoginRequest: + google.protobuf.Timestamp: + type: string + format: date-time + description: |- + A Timestamp represents a point in time independent of any time zone or local + calendar, encoded as a count of seconds and fractions of seconds at + nanosecond resolution. The count is relative to an epoch at UTC midnight on + January 1, 1970, in the proleptic Gregorian calendar which extends the + Gregorian calendar backwards to year one. + + All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap + second table is needed for interpretation, using a [24-hour linear + smear](https://developers.google.com/time/smear). + + The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By + restricting to that range, we ensure that we can convert to and from [RFC + 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. + + # Examples + + Example 1: Compute Timestamp from POSIX `time()`. + + Timestamp timestamp; + timestamp.set_seconds(time(NULL)); + timestamp.set_nanos(0); + + Example 2: Compute Timestamp from POSIX `gettimeofday()`. + + struct timeval tv; + gettimeofday(&tv, NULL); + + Timestamp timestamp; + timestamp.set_seconds(tv.tv_sec); + timestamp.set_nanos(tv.tv_usec * 1000); + + Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. + + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; + + // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z + // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. + Timestamp timestamp; + timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); + timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); + + Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. + + long millis = System.currentTimeMillis(); + + Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) + .setNanos((int) ((millis % 1000) * 1000000)).build(); + + Example 5: Compute Timestamp from Java `Instant.now()`. + + Instant now = Instant.now(); + + Timestamp timestamp = + Timestamp.newBuilder().setSeconds(now.getEpochSecond()) + .setNanos(now.getNano()).build(); + + Example 6: Compute Timestamp from current time in Python. + + timestamp = Timestamp() + timestamp.GetCurrentTime() + + # JSON Mapping + + In JSON format, the Timestamp type is encoded as a string in the + [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the + format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" + where {year} is always expressed using four digits while {month}, {day}, + {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional + seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), + are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone + is required. A proto3 JSON serializer should always use UTC (as indicated by + "Z") when printing the Timestamp type and a proto3 JSON parser should be + able to accept both UTC and other timezones (as indicated by an offset). + + For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past + 01:30 UTC on January 15, 2017. + + In JavaScript, one can convert a Date object to this format using the + standard + [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) + method. In Python, a standard `datetime.datetime` object can be converted + to this format using + [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with + the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use + the Joda Time's [`ISODateTimeFormat.dateTime()`]( + http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime() + ) to obtain a formatter capable of generating timestamps in this format. + item.v1.CreateItemRequest: type: object properties: - username: - type: string - title: username - password: - type: string - title: password - title: LoginRequest + item: + title: item + $ref: '#/components/schemas/item.v1.Item' + title: CreateItemRequest additionalProperties: false - user.v1.LoginResponse: + item.v1.CreateItemResponse: type: object properties: - token: - type: string - title: token - title: LoginResponse + item: + title: item + $ref: '#/components/schemas/item.v1.Item' + title: CreateItemResponse additionalProperties: false - user.v1.LogoutRequest: - type: object - title: LogoutRequest - additionalProperties: false - user.v1.LogoutResponse: - type: object - title: LogoutResponse - additionalProperties: false - user.v1.SignUpRequest: + item.v1.DeleteItemRequest: type: object properties: - username: - type: string - title: username - password: - type: string - title: password - confirmPassword: - type: string - title: confirm_password - title: SignUpRequest + id: + type: integer + title: id + title: DeleteItemRequest additionalProperties: false - user.v1.SignUpResponse: + item.v1.DeleteItemResponse: type: object - title: SignUpResponse + title: DeleteItemResponse + additionalProperties: false + item.v1.GetItemRequest: + type: object + properties: + id: + type: integer + title: id + title: GetItemRequest + additionalProperties: false + item.v1.GetItemResponse: + type: object + properties: + item: + title: item + $ref: '#/components/schemas/item.v1.Item' + title: GetItemResponse + additionalProperties: false + item.v1.GetItemsRequest: + type: object + properties: + start: + title: start + nullable: true + $ref: '#/components/schemas/google.protobuf.Timestamp' + end: + title: end + nullable: true + $ref: '#/components/schemas/google.protobuf.Timestamp' + filter: + type: string + title: filter + nullable: true + limit: + type: integer + title: limit + nullable: true + offset: + type: integer + title: offset + nullable: true + title: GetItemsRequest + additionalProperties: false + item.v1.GetItemsResponse: + type: object + properties: + items: + type: array + items: + $ref: '#/components/schemas/item.v1.Item' + title: items + count: + type: + - integer + - string + title: count + format: int64 + title: GetItemsResponse + additionalProperties: false + item.v1.Item: + type: object + properties: + id: + type: integer + title: id + nullable: true + name: + type: string + title: name + description: + type: string + title: description + price: + type: number + title: price + format: float + quantity: + type: integer + title: quantity + added: + title: added + nullable: true + $ref: '#/components/schemas/google.protobuf.Timestamp' + title: Item + additionalProperties: false + item.v1.UpdateItemRequest: + type: object + properties: + item: + title: item + $ref: '#/components/schemas/item.v1.Item' + title: UpdateItemRequest + additionalProperties: false + item.v1.UpdateItemResponse: + type: object + properties: + item: + title: item + $ref: '#/components/schemas/item.v1.Item' + title: UpdateItemResponse additionalProperties: false connect-protocol-version: type: number @@ -117,6 +292,51 @@ components: additionalProperties: true additionalProperties: true description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + user.v1.LoginRequest: + type: object + properties: + username: + type: string + title: username + password: + type: string + title: password + title: LoginRequest + additionalProperties: false + user.v1.LoginResponse: + type: object + properties: + token: + type: string + title: token + title: LoginResponse + additionalProperties: false + user.v1.LogoutRequest: + type: object + title: LogoutRequest + additionalProperties: false + user.v1.LogoutResponse: + type: object + title: LogoutResponse + additionalProperties: false + user.v1.SignUpRequest: + type: object + properties: + username: + type: string + title: username + password: + type: string + title: password + confirmPassword: + type: string + title: confirm_password + title: SignUpRequest + additionalProperties: false + user.v1.SignUpResponse: + type: object + title: SignUpResponse + additionalProperties: false user.v1.APIKeyRequest: type: object properties: @@ -157,6 +377,181 @@ components: security: - bearerAuth: [] paths: + /item.v1.ItemService/GetItem: + post: + tags: + - item.v1.ItemService + summary: GetItem + operationId: item.v1.ItemService.GetItem + parameters: + - name: Connect-Protocol-Version + in: header + required: true + schema: + $ref: '#/components/schemas/connect-protocol-version' + - name: Connect-Timeout-Ms + in: header + schema: + $ref: '#/components/schemas/connect-timeout-header' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/item.v1.GetItemRequest' + required: true + responses: + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/item.v1.GetItemResponse' + /item.v1.ItemService/GetItems: + post: + tags: + - item.v1.ItemService + summary: GetItems + operationId: item.v1.ItemService.GetItems + parameters: + - name: Connect-Protocol-Version + in: header + required: true + schema: + $ref: '#/components/schemas/connect-protocol-version' + - name: Connect-Timeout-Ms + in: header + schema: + $ref: '#/components/schemas/connect-timeout-header' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/item.v1.GetItemsRequest' + required: true + responses: + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/item.v1.GetItemsResponse' + /item.v1.ItemService/CreateItem: + post: + tags: + - item.v1.ItemService + summary: CreateItem + operationId: item.v1.ItemService.CreateItem + parameters: + - name: Connect-Protocol-Version + in: header + required: true + schema: + $ref: '#/components/schemas/connect-protocol-version' + - name: Connect-Timeout-Ms + in: header + schema: + $ref: '#/components/schemas/connect-timeout-header' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/item.v1.CreateItemRequest' + required: true + responses: + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/item.v1.CreateItemResponse' + /item.v1.ItemService/UpdateItem: + post: + tags: + - item.v1.ItemService + summary: UpdateItem + operationId: item.v1.ItemService.UpdateItem + parameters: + - name: Connect-Protocol-Version + in: header + required: true + schema: + $ref: '#/components/schemas/connect-protocol-version' + - name: Connect-Timeout-Ms + in: header + schema: + $ref: '#/components/schemas/connect-timeout-header' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/item.v1.UpdateItemRequest' + required: true + responses: + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/item.v1.UpdateItemResponse' + /item.v1.ItemService/DeleteItem: + post: + tags: + - item.v1.ItemService + summary: DeleteItem + operationId: item.v1.ItemService.DeleteItem + parameters: + - name: Connect-Protocol-Version + in: header + required: true + schema: + $ref: '#/components/schemas/connect-protocol-version' + - name: Connect-Timeout-Ms + in: header + schema: + $ref: '#/components/schemas/connect-timeout-header' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/item.v1.DeleteItemRequest' + required: true + responses: + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/item.v1.DeleteItemResponse' /user.v1.AuthService/Login: post: tags: @@ -333,5 +728,6 @@ paths: schema: $ref: '#/components/schemas/user.v1.APIKeyResponse' tags: + - name: item.v1.ItemService - name: user.v1.AuthService - name: user.v1.UserService diff --git a/flake.nix b/flake.nix index 31e8a02..b32faec 100644 --- a/flake.nix +++ b/flake.nix @@ -68,9 +68,6 @@ # Svelte frontend nodejs_22 - # Openapi gen - openapi-generator-cli - # Helper scripts (writeShellApplication { name = "run"; @@ -91,6 +88,8 @@ wait $P1 wait $P2 wait $P3 + + kill $P1 $P2 $P3 ''; }) diff --git a/proto/item/v1/item.proto b/proto/item/v1/item.proto new file mode 100644 index 0000000..ab407e8 --- /dev/null +++ b/proto/item/v1/item.proto @@ -0,0 +1,60 @@ +syntax = "proto3"; + +package item.v1; + +import "google/protobuf/timestamp.proto"; + +message Item { + optional uint32 id = 1; + string name = 2; + string description = 3; + float price = 4; + uint32 quantity = 5; + optional google.protobuf.Timestamp added = 6; +} + +service ItemService { + rpc GetItem (GetItemRequest) returns (GetItemResponse) {} + rpc GetItems (GetItemsRequest) returns (GetItemsResponse) {} + rpc CreateItem (CreateItemRequest) returns (CreateItemResponse) {} + rpc UpdateItem (UpdateItemRequest) returns (UpdateItemResponse) {} + rpc DeleteItem (DeleteItemRequest) returns (DeleteItemResponse) {} +} + +message GetItemRequest { + uint32 id = 1; +} +message GetItemResponse { + Item item = 1; +} + +message GetItemsRequest { + optional google.protobuf.Timestamp start = 1; + optional google.protobuf.Timestamp end = 2; + optional string filter = 3; + optional uint32 limit = 4; + optional uint32 offset = 5; +} +message GetItemsResponse { + repeated Item items = 1; + uint64 count = 2; +} + +message CreateItemRequest { + Item item = 1; +} +message CreateItemResponse { + Item item = 1; +} + +message UpdateItemRequest { + Item item = 1; +} +message UpdateItemResponse { + Item item = 1; +} + +message DeleteItemRequest { + uint32 id = 1; +} +message DeleteItemResponse {} \ No newline at end of file diff --git a/server/internal/database/migrate.go b/server/internal/database/migrate.go index 44b3068..8ad3f9d 100644 --- a/server/internal/database/migrate.go +++ b/server/internal/database/migrate.go @@ -6,7 +6,7 @@ import ( ) func Migrate(db *gorm.DB) error { - err := db.AutoMigrate(&models.User{}) + err := db.AutoMigrate(&models.User{}, &models.Item{}) if err != nil { return err } diff --git a/server/internal/handlers/item.go b/server/internal/handlers/item.go new file mode 100644 index 0000000..612a452 --- /dev/null +++ b/server/internal/handlers/item.go @@ -0,0 +1,171 @@ +package handlers + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "connectrpc.com/connect" + "github.com/spotdemo4/trevstack/server/internal/interceptors" + "github.com/spotdemo4/trevstack/server/internal/models" + itemv1 "github.com/spotdemo4/trevstack/server/internal/services/item/v1" + "github.com/spotdemo4/trevstack/server/internal/services/item/v1/itemv1connect" + "gorm.io/gorm" +) + +type ItemHandler struct { + db *gorm.DB + key []byte +} + +func (h *ItemHandler) GetItem(ctx context.Context, req *connect.Request[itemv1.GetItemRequest]) (*connect.Response[itemv1.GetItemResponse], error) { + userid, ok := interceptors.UserFromContext(ctx) + if !ok { + return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("unauthenticated")) + } + + // Get item + item := models.Item{} + if err := h.db.First(&item, "id = ? AND user_id = ?", req.Msg.Id, userid).Error; err != nil { + return nil, connect.NewError(connect.CodeNotFound, err) + } + + res := connect.NewResponse(&itemv1.GetItemResponse{ + Item: item.ToConnectV1(), + }) + return res, nil +} + +func (h *ItemHandler) GetItems(ctx context.Context, req *connect.Request[itemv1.GetItemsRequest]) (*connect.Response[itemv1.GetItemsResponse], error) { + userid, ok := interceptors.UserFromContext(ctx) + if !ok { + return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("unauthenticated")) + } + + // Filters + sql := h.db.Where("user_id = ?", userid) + if req.Msg.Start != nil { + sql = sql.Where("added >= ?", req.Msg.Start.AsTime()) + } + if req.Msg.End != nil { + sql = sql.Where("added <= ?", req.Msg.End.AsTime()) + } + if req.Msg.Filter != nil { + sql = sql.Where("name LIKE ?", fmt.Sprintf("%%%s%%", *req.Msg.Filter)) + } + + // Uncounted filters + sqlu := sql.Session(&gorm.Session{}) + if req.Msg.Limit != nil { + sqlu = sqlu.Limit(int(*req.Msg.Limit)) + } + if req.Msg.Offset != nil { + sqlu = sqlu.Offset(int(*req.Msg.Offset)) + } + + // Get items & count + items := []models.Item{} + var count int64 + if err := sqlu.Order("added desc").Find(&items).Error; err != nil { + return nil, connect.NewError(connect.CodeNotFound, err) + } + if err := sql.Model(&items).Count(&count).Error; err != nil { + return nil, connect.NewError(connect.CodeInternal, err) + } + + // Convert to connect v1 items + resItems := []*itemv1.Item{} + for _, item := range items { + resItems = append(resItems, item.ToConnectV1()) + } + + res := connect.NewResponse(&itemv1.GetItemsResponse{ + Items: resItems, + Count: uint64(count), + }) + return res, nil +} + +func (h *ItemHandler) CreateItem(ctx context.Context, req *connect.Request[itemv1.CreateItemRequest]) (*connect.Response[itemv1.CreateItemResponse], error) { + userid, ok := interceptors.UserFromContext(ctx) + if !ok { + return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("unauthenticated")) + } + + // Create item + item := models.Item{ + Name: req.Msg.Item.Name, + Description: req.Msg.Item.Description, + Price: req.Msg.Item.Price, + Quantity: int(req.Msg.Item.Quantity), + Added: time.Now(), + UserID: uint(userid), + } + if err := h.db.Create(&item).Error; err != nil { + return nil, connect.NewError(connect.CodeInternal, err) + } + + res := connect.NewResponse(&itemv1.CreateItemResponse{ + Item: item.ToConnectV1(), + }) + return res, nil +} + +func (h *ItemHandler) UpdateItem(ctx context.Context, req *connect.Request[itemv1.UpdateItemRequest]) (*connect.Response[itemv1.UpdateItemResponse], error) { + userid, ok := interceptors.UserFromContext(ctx) + if !ok { + return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("unauthenticated")) + } + + // Validate + if req.Msg.Item.Id == nil { + return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("id is required")) + } + + // Update item + item := models.Item{ + ID: *req.Msg.Item.Id, + Name: req.Msg.Item.Name, + Description: req.Msg.Item.Description, + Price: req.Msg.Item.Price, + Quantity: int(req.Msg.Item.Quantity), + UserID: uint(userid), + } + if err := h.db.Where("id = ? AND user_id = ?", req.Msg.Item.Id, userid).Updates(&item).Error; err != nil { + return nil, connect.NewError(connect.CodeInternal, err) + } + + res := connect.NewResponse(&itemv1.UpdateItemResponse{ + Item: item.ToConnectV1(), + }) + return res, nil +} + +func (h *ItemHandler) DeleteItem(ctx context.Context, req *connect.Request[itemv1.DeleteItemRequest]) (*connect.Response[itemv1.DeleteItemResponse], error) { + userid, ok := interceptors.UserFromContext(ctx) + if !ok { + return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("unauthenticated")) + } + + // Delete item + if err := h.db.Delete(&models.Item{}, "id = ? AND user_id = ?", req.Msg.Id, userid).Error; err != nil { + return nil, connect.NewError(connect.CodeInternal, err) + } + + res := connect.NewResponse(&itemv1.DeleteItemResponse{}) + return res, nil +} + +func NewItemHandler(db *gorm.DB, key string) (string, http.Handler) { + interceptors := connect.WithInterceptors(interceptors.NewAuthInterceptor(key)) + + return itemv1connect.NewItemServiceHandler( + &ItemHandler{ + db: db, + key: []byte(key), + }, + interceptors, + ) +} diff --git a/server/internal/interceptors/auth.go b/server/internal/interceptors/auth.go index de2abb5..67d1db7 100644 --- a/server/internal/interceptors/auth.go +++ b/server/internal/interceptors/auth.go @@ -24,11 +24,12 @@ func WithAuthRedirect(next http.Handler, key string) http.Handler { } switch pathItems[1] { - case "auth": - next.ServeHTTP(w, r) - return + case "auth": + fallthrough case "_app": + fallthrough + case "favicon.png": next.ServeHTTP(w, r) return diff --git a/server/internal/interceptors/ratelimit.go b/server/internal/interceptors/ratelimit.go index 74138f6..129ca37 100644 --- a/server/internal/interceptors/ratelimit.go +++ b/server/internal/interceptors/ratelimit.go @@ -2,7 +2,7 @@ package interceptors import ( "context" - "log" + "errors" "sync" "time" @@ -44,8 +44,11 @@ func (i *ratelimitInterceptor) WrapUnary(next connect.UnaryFunc) connect.UnaryFu return next(ctx, req) } - // Get ip - log.Println(req.Peer().Addr) + // Get user agent + limiter := i.getVisitor(req.Header().Get("User-Agent")) + if limiter.Allow() == false { + return nil, connect.NewError(connect.CodeResourceExhausted, errors.New("rate limit exceeded")) + } return next(ctx, req) }) @@ -65,22 +68,25 @@ func (i *ratelimitInterceptor) WrapStreamingHandler(next connect.StreamingHandle ctx context.Context, conn connect.StreamingHandlerConn, ) error { - // Get ip - log.Println(conn.Peer().Query) + // Get user agent + limiter := i.getVisitor(conn.RequestHeader().Get("User-Agent")) + if limiter.Allow() == false { + return connect.NewError(connect.CodeResourceExhausted, errors.New("rate limit exceeded")) + } return next(ctx, conn) }) } -func (i *ratelimitInterceptor) getVisitor(ip string) *rate.Limiter { +func (i *ratelimitInterceptor) getVisitor(userAgent string) *rate.Limiter { i.mu.Lock() defer i.mu.Unlock() - v, exists := i.visitors[ip] + v, exists := i.visitors[userAgent] if !exists { limiter := rate.NewLimiter(1, 3) // Include the current time when creating a new visitor. - i.visitors[ip] = &visitor{limiter, time.Now()} + i.visitors[userAgent] = &visitor{limiter, time.Now()} return limiter } diff --git a/server/internal/models/item.go b/server/internal/models/item.go new file mode 100644 index 0000000..f25a907 --- /dev/null +++ b/server/internal/models/item.go @@ -0,0 +1,33 @@ +package models + +import ( + "time" + + itemv1 "github.com/spotdemo4/trevstack/server/internal/services/item/v1" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type Item struct { + ID uint32 `gorm:"primaryKey"` + + Name string + Description string + Price float32 + Quantity int + Added time.Time + + // User + UserID uint + User User +} + +func (i Item) ToConnectV1() *itemv1.Item { + return &itemv1.Item{ + Id: &i.ID, + Name: i.Name, + Description: i.Description, + Price: i.Price, + Quantity: uint32(i.Quantity), + Added: timestamppb.New(i.Added), + } +} diff --git a/server/internal/services/item/v1/item.pb.go b/server/internal/services/item/v1/item.pb.go new file mode 100644 index 0000000..fcaadd0 --- /dev/null +++ b/server/internal/services/item/v1/item.pb.go @@ -0,0 +1,759 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.5 +// protoc (unknown) +// source: item/v1/item.proto + +package itemv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Item struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id *uint32 `protobuf:"varint,1,opt,name=id,proto3,oneof" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + Price float32 `protobuf:"fixed32,4,opt,name=price,proto3" json:"price,omitempty"` + Quantity uint32 `protobuf:"varint,5,opt,name=quantity,proto3" json:"quantity,omitempty"` + Added *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=added,proto3,oneof" json:"added,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Item) Reset() { + *x = Item{} + mi := &file_item_v1_item_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Item) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Item) ProtoMessage() {} + +func (x *Item) ProtoReflect() protoreflect.Message { + mi := &file_item_v1_item_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Item.ProtoReflect.Descriptor instead. +func (*Item) Descriptor() ([]byte, []int) { + return file_item_v1_item_proto_rawDescGZIP(), []int{0} +} + +func (x *Item) GetId() uint32 { + if x != nil && x.Id != nil { + return *x.Id + } + return 0 +} + +func (x *Item) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Item) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *Item) GetPrice() float32 { + if x != nil { + return x.Price + } + return 0 +} + +func (x *Item) GetQuantity() uint32 { + if x != nil { + return x.Quantity + } + return 0 +} + +func (x *Item) GetAdded() *timestamppb.Timestamp { + if x != nil { + return x.Added + } + return nil +} + +type GetItemRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetItemRequest) Reset() { + *x = GetItemRequest{} + mi := &file_item_v1_item_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetItemRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetItemRequest) ProtoMessage() {} + +func (x *GetItemRequest) ProtoReflect() protoreflect.Message { + mi := &file_item_v1_item_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetItemRequest.ProtoReflect.Descriptor instead. +func (*GetItemRequest) Descriptor() ([]byte, []int) { + return file_item_v1_item_proto_rawDescGZIP(), []int{1} +} + +func (x *GetItemRequest) GetId() uint32 { + if x != nil { + return x.Id + } + return 0 +} + +type GetItemResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Item *Item `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetItemResponse) Reset() { + *x = GetItemResponse{} + mi := &file_item_v1_item_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetItemResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetItemResponse) ProtoMessage() {} + +func (x *GetItemResponse) ProtoReflect() protoreflect.Message { + mi := &file_item_v1_item_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetItemResponse.ProtoReflect.Descriptor instead. +func (*GetItemResponse) Descriptor() ([]byte, []int) { + return file_item_v1_item_proto_rawDescGZIP(), []int{2} +} + +func (x *GetItemResponse) GetItem() *Item { + if x != nil { + return x.Item + } + return nil +} + +type GetItemsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Start *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=start,proto3,oneof" json:"start,omitempty"` + End *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=end,proto3,oneof" json:"end,omitempty"` + Filter *string `protobuf:"bytes,3,opt,name=filter,proto3,oneof" json:"filter,omitempty"` + Limit *uint32 `protobuf:"varint,4,opt,name=limit,proto3,oneof" json:"limit,omitempty"` + Offset *uint32 `protobuf:"varint,5,opt,name=offset,proto3,oneof" json:"offset,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetItemsRequest) Reset() { + *x = GetItemsRequest{} + mi := &file_item_v1_item_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetItemsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetItemsRequest) ProtoMessage() {} + +func (x *GetItemsRequest) ProtoReflect() protoreflect.Message { + mi := &file_item_v1_item_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetItemsRequest.ProtoReflect.Descriptor instead. +func (*GetItemsRequest) Descriptor() ([]byte, []int) { + return file_item_v1_item_proto_rawDescGZIP(), []int{3} +} + +func (x *GetItemsRequest) GetStart() *timestamppb.Timestamp { + if x != nil { + return x.Start + } + return nil +} + +func (x *GetItemsRequest) GetEnd() *timestamppb.Timestamp { + if x != nil { + return x.End + } + return nil +} + +func (x *GetItemsRequest) GetFilter() string { + if x != nil && x.Filter != nil { + return *x.Filter + } + return "" +} + +func (x *GetItemsRequest) GetLimit() uint32 { + if x != nil && x.Limit != nil { + return *x.Limit + } + return 0 +} + +func (x *GetItemsRequest) GetOffset() uint32 { + if x != nil && x.Offset != nil { + return *x.Offset + } + return 0 +} + +type GetItemsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Items []*Item `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + Count uint64 `protobuf:"varint,2,opt,name=count,proto3" json:"count,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetItemsResponse) Reset() { + *x = GetItemsResponse{} + mi := &file_item_v1_item_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetItemsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetItemsResponse) ProtoMessage() {} + +func (x *GetItemsResponse) ProtoReflect() protoreflect.Message { + mi := &file_item_v1_item_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetItemsResponse.ProtoReflect.Descriptor instead. +func (*GetItemsResponse) Descriptor() ([]byte, []int) { + return file_item_v1_item_proto_rawDescGZIP(), []int{4} +} + +func (x *GetItemsResponse) GetItems() []*Item { + if x != nil { + return x.Items + } + return nil +} + +func (x *GetItemsResponse) GetCount() uint64 { + if x != nil { + return x.Count + } + return 0 +} + +type CreateItemRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Item *Item `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateItemRequest) Reset() { + *x = CreateItemRequest{} + mi := &file_item_v1_item_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateItemRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateItemRequest) ProtoMessage() {} + +func (x *CreateItemRequest) ProtoReflect() protoreflect.Message { + mi := &file_item_v1_item_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateItemRequest.ProtoReflect.Descriptor instead. +func (*CreateItemRequest) Descriptor() ([]byte, []int) { + return file_item_v1_item_proto_rawDescGZIP(), []int{5} +} + +func (x *CreateItemRequest) GetItem() *Item { + if x != nil { + return x.Item + } + return nil +} + +type CreateItemResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Item *Item `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateItemResponse) Reset() { + *x = CreateItemResponse{} + mi := &file_item_v1_item_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateItemResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateItemResponse) ProtoMessage() {} + +func (x *CreateItemResponse) ProtoReflect() protoreflect.Message { + mi := &file_item_v1_item_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateItemResponse.ProtoReflect.Descriptor instead. +func (*CreateItemResponse) Descriptor() ([]byte, []int) { + return file_item_v1_item_proto_rawDescGZIP(), []int{6} +} + +func (x *CreateItemResponse) GetItem() *Item { + if x != nil { + return x.Item + } + return nil +} + +type UpdateItemRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Item *Item `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateItemRequest) Reset() { + *x = UpdateItemRequest{} + mi := &file_item_v1_item_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateItemRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateItemRequest) ProtoMessage() {} + +func (x *UpdateItemRequest) ProtoReflect() protoreflect.Message { + mi := &file_item_v1_item_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateItemRequest.ProtoReflect.Descriptor instead. +func (*UpdateItemRequest) Descriptor() ([]byte, []int) { + return file_item_v1_item_proto_rawDescGZIP(), []int{7} +} + +func (x *UpdateItemRequest) GetItem() *Item { + if x != nil { + return x.Item + } + return nil +} + +type UpdateItemResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Item *Item `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateItemResponse) Reset() { + *x = UpdateItemResponse{} + mi := &file_item_v1_item_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateItemResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateItemResponse) ProtoMessage() {} + +func (x *UpdateItemResponse) ProtoReflect() protoreflect.Message { + mi := &file_item_v1_item_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateItemResponse.ProtoReflect.Descriptor instead. +func (*UpdateItemResponse) Descriptor() ([]byte, []int) { + return file_item_v1_item_proto_rawDescGZIP(), []int{8} +} + +func (x *UpdateItemResponse) GetItem() *Item { + if x != nil { + return x.Item + } + return nil +} + +type DeleteItemRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteItemRequest) Reset() { + *x = DeleteItemRequest{} + mi := &file_item_v1_item_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteItemRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteItemRequest) ProtoMessage() {} + +func (x *DeleteItemRequest) ProtoReflect() protoreflect.Message { + mi := &file_item_v1_item_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteItemRequest.ProtoReflect.Descriptor instead. +func (*DeleteItemRequest) Descriptor() ([]byte, []int) { + return file_item_v1_item_proto_rawDescGZIP(), []int{9} +} + +func (x *DeleteItemRequest) GetId() uint32 { + if x != nil { + return x.Id + } + return 0 +} + +type DeleteItemResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteItemResponse) Reset() { + *x = DeleteItemResponse{} + mi := &file_item_v1_item_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteItemResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteItemResponse) ProtoMessage() {} + +func (x *DeleteItemResponse) ProtoReflect() protoreflect.Message { + mi := &file_item_v1_item_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteItemResponse.ProtoReflect.Descriptor instead. +func (*DeleteItemResponse) Descriptor() ([]byte, []int) { + return file_item_v1_item_proto_rawDescGZIP(), []int{10} +} + +var File_item_v1_item_proto protoreflect.FileDescriptor + +var file_item_v1_item_proto_rawDesc = string([]byte{ + 0x0a, 0x12, 0x69, 0x74, 0x65, 0x6d, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xcb, + 0x01, 0x0a, 0x04, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x13, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0d, 0x48, 0x00, 0x52, 0x02, 0x69, 0x64, 0x88, 0x01, 0x01, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x02, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x12, 0x35, 0x0a, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x48, + 0x01, 0x52, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x88, 0x01, 0x01, 0x42, 0x05, 0x0a, 0x03, 0x5f, + 0x69, 0x64, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x61, 0x64, 0x64, 0x65, 0x64, 0x22, 0x20, 0x0a, 0x0e, + 0x47, 0x65, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x22, 0x34, + 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x21, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x0d, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, + 0x69, 0x74, 0x65, 0x6d, 0x22, 0x82, 0x02, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x49, 0x74, 0x65, 0x6d, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x48, 0x00, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x88, 0x01, 0x01, 0x12, + 0x31, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x48, 0x01, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x88, + 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x48, 0x02, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, + 0x19, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x03, + 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x6f, 0x66, + 0x66, 0x73, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x04, 0x52, 0x06, 0x6f, 0x66, + 0x66, 0x73, 0x65, 0x74, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x65, 0x6e, 0x64, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x66, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x42, 0x09, + 0x0a, 0x07, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x4d, 0x0a, 0x10, 0x47, 0x65, 0x74, + 0x49, 0x74, 0x65, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, + 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x69, + 0x74, 0x65, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, + 0x6d, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x36, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, + 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x69, 0x74, + 0x65, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, + 0x22, 0x37, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x49, + 0x74, 0x65, 0x6d, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x36, 0x0a, 0x11, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, + 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x69, + 0x74, 0x65, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, 0x69, 0x74, 0x65, + 0x6d, 0x22, 0x37, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x76, 0x31, 0x2e, + 0x49, 0x74, 0x65, 0x6d, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x23, 0x0a, 0x11, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x22, + 0x14, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xeb, 0x02, 0x0a, 0x0b, 0x49, 0x74, 0x65, 0x6d, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x49, 0x74, 0x65, 0x6d, + 0x12, 0x17, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x74, + 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x69, 0x74, 0x65, 0x6d, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x49, 0x74, 0x65, 0x6d, + 0x73, 0x12, 0x18, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x49, + 0x74, 0x65, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x69, 0x74, + 0x65, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1a, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x47, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, + 0x1a, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x69, 0x74, + 0x65, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0a, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1a, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x2e, + 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x42, 0x9d, 0x01, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, 0x2e, 0x69, 0x74, 0x65, 0x6d, + 0x2e, 0x76, 0x31, 0x42, 0x09, 0x49, 0x74, 0x65, 0x6d, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, + 0x5a, 0x46, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x70, 0x6f, + 0x74, 0x64, 0x65, 0x6d, 0x6f, 0x34, 0x2f, 0x74, 0x72, 0x65, 0x76, 0x73, 0x74, 0x61, 0x63, 0x6b, + 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x69, 0x74, 0x65, 0x6d, 0x2f, 0x76, + 0x31, 0x3b, 0x69, 0x74, 0x65, 0x6d, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x49, 0x58, 0x58, 0xaa, 0x02, + 0x07, 0x49, 0x74, 0x65, 0x6d, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x07, 0x49, 0x74, 0x65, 0x6d, 0x5c, + 0x56, 0x31, 0xe2, 0x02, 0x13, 0x49, 0x74, 0x65, 0x6d, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x08, 0x49, 0x74, 0x65, 0x6d, 0x3a, + 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +}) + +var ( + file_item_v1_item_proto_rawDescOnce sync.Once + file_item_v1_item_proto_rawDescData []byte +) + +func file_item_v1_item_proto_rawDescGZIP() []byte { + file_item_v1_item_proto_rawDescOnce.Do(func() { + file_item_v1_item_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_item_v1_item_proto_rawDesc), len(file_item_v1_item_proto_rawDesc))) + }) + return file_item_v1_item_proto_rawDescData +} + +var file_item_v1_item_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_item_v1_item_proto_goTypes = []any{ + (*Item)(nil), // 0: item.v1.Item + (*GetItemRequest)(nil), // 1: item.v1.GetItemRequest + (*GetItemResponse)(nil), // 2: item.v1.GetItemResponse + (*GetItemsRequest)(nil), // 3: item.v1.GetItemsRequest + (*GetItemsResponse)(nil), // 4: item.v1.GetItemsResponse + (*CreateItemRequest)(nil), // 5: item.v1.CreateItemRequest + (*CreateItemResponse)(nil), // 6: item.v1.CreateItemResponse + (*UpdateItemRequest)(nil), // 7: item.v1.UpdateItemRequest + (*UpdateItemResponse)(nil), // 8: item.v1.UpdateItemResponse + (*DeleteItemRequest)(nil), // 9: item.v1.DeleteItemRequest + (*DeleteItemResponse)(nil), // 10: item.v1.DeleteItemResponse + (*timestamppb.Timestamp)(nil), // 11: google.protobuf.Timestamp +} +var file_item_v1_item_proto_depIdxs = []int32{ + 11, // 0: item.v1.Item.added:type_name -> google.protobuf.Timestamp + 0, // 1: item.v1.GetItemResponse.item:type_name -> item.v1.Item + 11, // 2: item.v1.GetItemsRequest.start:type_name -> google.protobuf.Timestamp + 11, // 3: item.v1.GetItemsRequest.end:type_name -> google.protobuf.Timestamp + 0, // 4: item.v1.GetItemsResponse.items:type_name -> item.v1.Item + 0, // 5: item.v1.CreateItemRequest.item:type_name -> item.v1.Item + 0, // 6: item.v1.CreateItemResponse.item:type_name -> item.v1.Item + 0, // 7: item.v1.UpdateItemRequest.item:type_name -> item.v1.Item + 0, // 8: item.v1.UpdateItemResponse.item:type_name -> item.v1.Item + 1, // 9: item.v1.ItemService.GetItem:input_type -> item.v1.GetItemRequest + 3, // 10: item.v1.ItemService.GetItems:input_type -> item.v1.GetItemsRequest + 5, // 11: item.v1.ItemService.CreateItem:input_type -> item.v1.CreateItemRequest + 7, // 12: item.v1.ItemService.UpdateItem:input_type -> item.v1.UpdateItemRequest + 9, // 13: item.v1.ItemService.DeleteItem:input_type -> item.v1.DeleteItemRequest + 2, // 14: item.v1.ItemService.GetItem:output_type -> item.v1.GetItemResponse + 4, // 15: item.v1.ItemService.GetItems:output_type -> item.v1.GetItemsResponse + 6, // 16: item.v1.ItemService.CreateItem:output_type -> item.v1.CreateItemResponse + 8, // 17: item.v1.ItemService.UpdateItem:output_type -> item.v1.UpdateItemResponse + 10, // 18: item.v1.ItemService.DeleteItem:output_type -> item.v1.DeleteItemResponse + 14, // [14:19] is the sub-list for method output_type + 9, // [9:14] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name +} + +func init() { file_item_v1_item_proto_init() } +func file_item_v1_item_proto_init() { + if File_item_v1_item_proto != nil { + return + } + file_item_v1_item_proto_msgTypes[0].OneofWrappers = []any{} + file_item_v1_item_proto_msgTypes[3].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_item_v1_item_proto_rawDesc), len(file_item_v1_item_proto_rawDesc)), + NumEnums: 0, + NumMessages: 11, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_item_v1_item_proto_goTypes, + DependencyIndexes: file_item_v1_item_proto_depIdxs, + MessageInfos: file_item_v1_item_proto_msgTypes, + }.Build() + File_item_v1_item_proto = out.File + file_item_v1_item_proto_goTypes = nil + file_item_v1_item_proto_depIdxs = nil +} diff --git a/server/internal/services/item/v1/itemv1connect/item.connect.go b/server/internal/services/item/v1/itemv1connect/item.connect.go new file mode 100644 index 0000000..4b6297b --- /dev/null +++ b/server/internal/services/item/v1/itemv1connect/item.connect.go @@ -0,0 +1,220 @@ +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: item/v1/item.proto + +package itemv1connect + +import ( + connect "connectrpc.com/connect" + context "context" + errors "errors" + v1 "github.com/spotdemo4/trevstack/server/internal/services/item/v1" + http "net/http" + strings "strings" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect.IsAtLeastVersion1_13_0 + +const ( + // ItemServiceName is the fully-qualified name of the ItemService service. + ItemServiceName = "item.v1.ItemService" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // ItemServiceGetItemProcedure is the fully-qualified name of the ItemService's GetItem RPC. + ItemServiceGetItemProcedure = "/item.v1.ItemService/GetItem" + // ItemServiceGetItemsProcedure is the fully-qualified name of the ItemService's GetItems RPC. + ItemServiceGetItemsProcedure = "/item.v1.ItemService/GetItems" + // ItemServiceCreateItemProcedure is the fully-qualified name of the ItemService's CreateItem RPC. + ItemServiceCreateItemProcedure = "/item.v1.ItemService/CreateItem" + // ItemServiceUpdateItemProcedure is the fully-qualified name of the ItemService's UpdateItem RPC. + ItemServiceUpdateItemProcedure = "/item.v1.ItemService/UpdateItem" + // ItemServiceDeleteItemProcedure is the fully-qualified name of the ItemService's DeleteItem RPC. + ItemServiceDeleteItemProcedure = "/item.v1.ItemService/DeleteItem" +) + +// ItemServiceClient is a client for the item.v1.ItemService service. +type ItemServiceClient interface { + GetItem(context.Context, *connect.Request[v1.GetItemRequest]) (*connect.Response[v1.GetItemResponse], error) + GetItems(context.Context, *connect.Request[v1.GetItemsRequest]) (*connect.Response[v1.GetItemsResponse], error) + CreateItem(context.Context, *connect.Request[v1.CreateItemRequest]) (*connect.Response[v1.CreateItemResponse], error) + UpdateItem(context.Context, *connect.Request[v1.UpdateItemRequest]) (*connect.Response[v1.UpdateItemResponse], error) + DeleteItem(context.Context, *connect.Request[v1.DeleteItemRequest]) (*connect.Response[v1.DeleteItemResponse], error) +} + +// NewItemServiceClient constructs a client for the item.v1.ItemService service. By default, it uses +// the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends +// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or +// connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewItemServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) ItemServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + itemServiceMethods := v1.File_item_v1_item_proto.Services().ByName("ItemService").Methods() + return &itemServiceClient{ + getItem: connect.NewClient[v1.GetItemRequest, v1.GetItemResponse]( + httpClient, + baseURL+ItemServiceGetItemProcedure, + connect.WithSchema(itemServiceMethods.ByName("GetItem")), + connect.WithClientOptions(opts...), + ), + getItems: connect.NewClient[v1.GetItemsRequest, v1.GetItemsResponse]( + httpClient, + baseURL+ItemServiceGetItemsProcedure, + connect.WithSchema(itemServiceMethods.ByName("GetItems")), + connect.WithClientOptions(opts...), + ), + createItem: connect.NewClient[v1.CreateItemRequest, v1.CreateItemResponse]( + httpClient, + baseURL+ItemServiceCreateItemProcedure, + connect.WithSchema(itemServiceMethods.ByName("CreateItem")), + connect.WithClientOptions(opts...), + ), + updateItem: connect.NewClient[v1.UpdateItemRequest, v1.UpdateItemResponse]( + httpClient, + baseURL+ItemServiceUpdateItemProcedure, + connect.WithSchema(itemServiceMethods.ByName("UpdateItem")), + connect.WithClientOptions(opts...), + ), + deleteItem: connect.NewClient[v1.DeleteItemRequest, v1.DeleteItemResponse]( + httpClient, + baseURL+ItemServiceDeleteItemProcedure, + connect.WithSchema(itemServiceMethods.ByName("DeleteItem")), + connect.WithClientOptions(opts...), + ), + } +} + +// itemServiceClient implements ItemServiceClient. +type itemServiceClient struct { + getItem *connect.Client[v1.GetItemRequest, v1.GetItemResponse] + getItems *connect.Client[v1.GetItemsRequest, v1.GetItemsResponse] + createItem *connect.Client[v1.CreateItemRequest, v1.CreateItemResponse] + updateItem *connect.Client[v1.UpdateItemRequest, v1.UpdateItemResponse] + deleteItem *connect.Client[v1.DeleteItemRequest, v1.DeleteItemResponse] +} + +// GetItem calls item.v1.ItemService.GetItem. +func (c *itemServiceClient) GetItem(ctx context.Context, req *connect.Request[v1.GetItemRequest]) (*connect.Response[v1.GetItemResponse], error) { + return c.getItem.CallUnary(ctx, req) +} + +// GetItems calls item.v1.ItemService.GetItems. +func (c *itemServiceClient) GetItems(ctx context.Context, req *connect.Request[v1.GetItemsRequest]) (*connect.Response[v1.GetItemsResponse], error) { + return c.getItems.CallUnary(ctx, req) +} + +// CreateItem calls item.v1.ItemService.CreateItem. +func (c *itemServiceClient) CreateItem(ctx context.Context, req *connect.Request[v1.CreateItemRequest]) (*connect.Response[v1.CreateItemResponse], error) { + return c.createItem.CallUnary(ctx, req) +} + +// UpdateItem calls item.v1.ItemService.UpdateItem. +func (c *itemServiceClient) UpdateItem(ctx context.Context, req *connect.Request[v1.UpdateItemRequest]) (*connect.Response[v1.UpdateItemResponse], error) { + return c.updateItem.CallUnary(ctx, req) +} + +// DeleteItem calls item.v1.ItemService.DeleteItem. +func (c *itemServiceClient) DeleteItem(ctx context.Context, req *connect.Request[v1.DeleteItemRequest]) (*connect.Response[v1.DeleteItemResponse], error) { + return c.deleteItem.CallUnary(ctx, req) +} + +// ItemServiceHandler is an implementation of the item.v1.ItemService service. +type ItemServiceHandler interface { + GetItem(context.Context, *connect.Request[v1.GetItemRequest]) (*connect.Response[v1.GetItemResponse], error) + GetItems(context.Context, *connect.Request[v1.GetItemsRequest]) (*connect.Response[v1.GetItemsResponse], error) + CreateItem(context.Context, *connect.Request[v1.CreateItemRequest]) (*connect.Response[v1.CreateItemResponse], error) + UpdateItem(context.Context, *connect.Request[v1.UpdateItemRequest]) (*connect.Response[v1.UpdateItemResponse], error) + DeleteItem(context.Context, *connect.Request[v1.DeleteItemRequest]) (*connect.Response[v1.DeleteItemResponse], error) +} + +// NewItemServiceHandler builds an HTTP handler from the service implementation. It returns the path +// on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewItemServiceHandler(svc ItemServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { + itemServiceMethods := v1.File_item_v1_item_proto.Services().ByName("ItemService").Methods() + itemServiceGetItemHandler := connect.NewUnaryHandler( + ItemServiceGetItemProcedure, + svc.GetItem, + connect.WithSchema(itemServiceMethods.ByName("GetItem")), + connect.WithHandlerOptions(opts...), + ) + itemServiceGetItemsHandler := connect.NewUnaryHandler( + ItemServiceGetItemsProcedure, + svc.GetItems, + connect.WithSchema(itemServiceMethods.ByName("GetItems")), + connect.WithHandlerOptions(opts...), + ) + itemServiceCreateItemHandler := connect.NewUnaryHandler( + ItemServiceCreateItemProcedure, + svc.CreateItem, + connect.WithSchema(itemServiceMethods.ByName("CreateItem")), + connect.WithHandlerOptions(opts...), + ) + itemServiceUpdateItemHandler := connect.NewUnaryHandler( + ItemServiceUpdateItemProcedure, + svc.UpdateItem, + connect.WithSchema(itemServiceMethods.ByName("UpdateItem")), + connect.WithHandlerOptions(opts...), + ) + itemServiceDeleteItemHandler := connect.NewUnaryHandler( + ItemServiceDeleteItemProcedure, + svc.DeleteItem, + connect.WithSchema(itemServiceMethods.ByName("DeleteItem")), + connect.WithHandlerOptions(opts...), + ) + return "/item.v1.ItemService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case ItemServiceGetItemProcedure: + itemServiceGetItemHandler.ServeHTTP(w, r) + case ItemServiceGetItemsProcedure: + itemServiceGetItemsHandler.ServeHTTP(w, r) + case ItemServiceCreateItemProcedure: + itemServiceCreateItemHandler.ServeHTTP(w, r) + case ItemServiceUpdateItemProcedure: + itemServiceUpdateItemHandler.ServeHTTP(w, r) + case ItemServiceDeleteItemProcedure: + itemServiceDeleteItemHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedItemServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedItemServiceHandler struct{} + +func (UnimplementedItemServiceHandler) GetItem(context.Context, *connect.Request[v1.GetItemRequest]) (*connect.Response[v1.GetItemResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("item.v1.ItemService.GetItem is not implemented")) +} + +func (UnimplementedItemServiceHandler) GetItems(context.Context, *connect.Request[v1.GetItemsRequest]) (*connect.Response[v1.GetItemsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("item.v1.ItemService.GetItems is not implemented")) +} + +func (UnimplementedItemServiceHandler) CreateItem(context.Context, *connect.Request[v1.CreateItemRequest]) (*connect.Response[v1.CreateItemResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("item.v1.ItemService.CreateItem is not implemented")) +} + +func (UnimplementedItemServiceHandler) UpdateItem(context.Context, *connect.Request[v1.UpdateItemRequest]) (*connect.Response[v1.UpdateItemResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("item.v1.ItemService.UpdateItem is not implemented")) +} + +func (UnimplementedItemServiceHandler) DeleteItem(context.Context, *connect.Request[v1.DeleteItemRequest]) (*connect.Response[v1.DeleteItemResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("item.v1.ItemService.DeleteItem is not implemented")) +} diff --git a/server/main.go b/server/main.go index 9b46d15..e00c921 100644 --- a/server/main.go +++ b/server/main.go @@ -114,6 +114,7 @@ func main() { api := http.NewServeMux() api.Handle(withCORS(handlers.NewAuthHandler(db, env.Key))) api.Handle(withCORS(handlers.NewUserHandler(db, env.Key))) + api.Handle(withCORS(handlers.NewItemHandler(db, env.Key))) // Serve web interface mux := http.NewServeMux() @@ -153,7 +154,9 @@ func main() { } }() - server.ListenAndServe() + if err := server.ListenAndServe(); err != nil { + log.Fatal(err) + } } // withCORS adds CORS support to a Connect HTTP handler.