Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,10 @@ RUN dotnet publish -c Release -o out
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /App
COPY --from=build-env /App/out .

RUN apt-get update && apt-get install -y curl

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl --fail http://localhost:8080/xrpc/_health || exit 1

ENTRYPOINT ["dotnet", "PinkSea.dll"]
6 changes: 6 additions & 0 deletions Dockerfile.Gateway
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,10 @@ COPY --from=build-env /App/out .

RUN mkdir wwwroot
COPY --from=frontend-build-env /App/dist ./wwwroot/

RUN apt-get update && apt-get install -y curl

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl --fail http://localhost:8080/xrpc/_health || exit 1

ENTRYPOINT ["dotnet", "PinkSea.Gateway.dll"]
7 changes: 7 additions & 0 deletions PinkSea.AtProto.Server/Xrpc/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ public static IEndpointRouteBuilder UseXrpcHandler(this IEndpointRouteBuilder ro
"/xrpc/{nsid}",
HandleXrpc);

routeBuilder.MapGet(
"/xrpc/_health",
() => new
{
version = "PinkSea"
});

return routeBuilder;
}

Expand Down
15 changes: 15 additions & 0 deletions PinkSea.AtProto.Shared/Lexicons/AtProto/GetRepoStatusRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Text.Json.Serialization;

namespace PinkSea.AtProto.Shared.Lexicons.AtProto;

/// <summary>
/// The request for the "com.atproto.sync.getRepoStatus" XRPC call.
/// </summary>
public class GetRepoStatusRequest
{
/// <summary>
/// The DID we're querying.
/// </summary>
[JsonPropertyName("did")]
public required string Did { get; set; }
}
27 changes: 27 additions & 0 deletions PinkSea.AtProto.Shared/Lexicons/AtProto/GetRepoStatusResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Text.Json.Serialization;

namespace PinkSea.AtProto.Shared.Lexicons.AtProto;

/// <summary>
/// A response for the "com.atproto.sync.getRepoStatus" XRPC call.
/// </summary>
public class GetRepoStatusResponse
{
/// <summary>
/// The DID for this repo.
/// </summary>
[JsonPropertyName("did")]
public required string Did { get; set; }

/// <summary>
/// Whether this repo is active.
/// </summary>
[JsonPropertyName("active")]
public required bool Active { get; set; }

/// <summary>
/// The status of the repo.
/// </summary>
[JsonPropertyName("status")]
public string? Status { get; set; }
}
30 changes: 27 additions & 3 deletions PinkSea.AtProto/Xrpc/Extensions/HttpResponseMessageExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,17 @@ public static async Task<XrpcErrorOr<TResponse>> ReadXrpcResponse<TResponse>(
var body = await message.Content.ReadAsStringAsync();
if (!message.IsSuccessStatusCode)
{
var error = JsonSerializer.Deserialize<XrpcError>(body) ?? new XrpcError
XrpcError? error = null;
try
{
error = JsonSerializer.Deserialize<XrpcError>(body);
}
catch (Exception e)
{
logger?.LogError(e, "Failed to deserialize JSON when parsing XRPC result.");
}

error ??= new XrpcError
{
Error = "UnknownError",
Message = body
Expand All @@ -43,7 +53,21 @@ public static async Task<XrpcErrorOr<TResponse>> ReadXrpcResponse<TResponse>(
if (message is TResponse typedResponse)
return XrpcErrorOr<TResponse>.Ok(typedResponse);

var result = JsonSerializer.Deserialize<TResponse>(body);
return XrpcErrorOr<TResponse>.Ok(result!);
try
{
var result = JsonSerializer.Deserialize<TResponse>(body);
return XrpcErrorOr<TResponse>.Ok(result!);
}
catch (Exception e)
{
logger?.LogError(e, "Failed to deserialize JSON when parsing XRPC result.");
var error = new XrpcError
{
Error = "UnknownError",
Message = body
};

return XrpcErrorOr<TResponse>.Fail(error.Error, error.Message);
}
}
}
46 changes: 46 additions & 0 deletions PinkSea.Frontend/src/api/atproto/lexicons.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import type { Oekaki } from '@/models/oekaki'
import type { SearchType } from '@/models/search-type'
import type { Author } from '@/models/author'
import type { TagSearchResult } from '@/models/tag-search-result'

declare module '@atcute/client/lexicons' {
type EmptyParams = object
Expand Down Expand Up @@ -101,6 +104,41 @@ declare module '@atcute/client/lexicons' {
}
}

// eslint-disable-next-line @typescript-eslint/no-namespace
namespace ComShinolabsPinkseaUnspeccedGetProfile {
interface Params {
did: string
}

interface Output {
did: string,
handle: string,
nick: string,
description: string,
links: [{
name: string,
url: string
}],
avatar: string
}
}

// eslint-disable-next-line @typescript-eslint/no-namespace
namespace ComShinolabsPinkseaGetSearchResults {
interface Params {
query: string,
type: SearchType,
since?: Date | null,
limit?: number | null
}

interface Output {
oekaki?: Oekaki[] | null,
tags?: TagSearchResult[] | null,
profiles?: Author[] | null
}
}

interface Queries {
'com.shinolabs.pinksea.getRecent': {
params: GenericTimelineQueryRequest,
Expand Down Expand Up @@ -133,6 +171,14 @@ declare module '@atcute/client/lexicons' {
'com.shinolabs.pinksea.getParentForReply': {
params: ComShinolabsPinkseaGetParentForReply.Params,
output: ComShinolabsPinkseaGetParentForReply.Params
},
'com.shinolabs.pinksea.unspecced.getProfile': {
params: ComShinolabsPinkseaUnspeccedGetProfile.Params,
output: ComShinolabsPinkseaUnspeccedGetProfile.Output
},
'com.shinolabs.pinksea.getSearchResults': {
params: ComShinolabsPinkseaGetSearchResults.Params,
output: ComShinolabsPinkseaGetSearchResults.Output
}
}

Expand Down
38 changes: 38 additions & 0 deletions PinkSea.Frontend/src/components/CardWithImage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<script setup lang="ts">

const props = defineProps<{
imageUrl: string
}>();

</script>

<template>
<div class="card">
<img :src="props.imageUrl">
<div class="card-slot">
<slot></slot>
</div>
</div>
</template>

<style scoped>
.card {
display: flex;
align-items: center;
border: 2px solid #FFB6C1;
width: 100%;
margin-top: 10px;
border-left: 0.525em solid #FFB6C1;
box-sizing: border-box;
position: relative;
padding: 5px;
}

.card > * {
margin-right: 10px;
}

.card > img {
max-width: 80px;
}
</style>
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
<script setup lang="ts">
const props = defineProps<{
image: string,
i18nKey: string
}>()
</script>

<template>
<div class="oekaki-card">
<img src="/assets/img/missing.png" />
<img :src="props.image" />
<div class="oekaki-meta">
{{ $t("post.this_post_no_longer_exists") }}
{{ $t(props.i18nKey) }}
</div>
</div>
</template>
Expand Down
24 changes: 24 additions & 0 deletions PinkSea.Frontend/src/components/search/SearchProfileCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script setup lang="ts">

import CardWithImage from '@/components/CardWithImage.vue'
import type { Author } from '@/models/author'

const props = defineProps<{
profile: Author
}>();
</script>

<template>
<CardWithImage image-url="https://sea.ata.moe/blank_avatar.png">
<div style="font-weight: bold">
{{ props.profile.handle }}
</div>
<div>
@{{ props.profile.handle }}
</div>
</CardWithImage>
</template>

<style scoped>

</style>
25 changes: 25 additions & 0 deletions PinkSea.Frontend/src/components/search/SearchTag.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<script setup lang="ts">

import type { TagSearchResult } from '@/models/tag-search-result'
import CardWithImage from '@/components/CardWithImage.vue'

const props = defineProps<{
tag: TagSearchResult
}>();

</script>

<template>
<CardWithImage :image-url="props.tag.oekaki.image">
<div style="font-weight: bold">
#{{ props.tag.tag }}
</div>
<div>
{{ props.tag.count }} posts under this tag.
</div>
</CardWithImage>
</template>

<style scoped>

</style>
8 changes: 7 additions & 1 deletion PinkSea.Frontend/src/intl/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@
"settings": "your settings",
"user_profile": "{{handle}}'s profile",
"user_post": "{{handle}}'s post",
"tagged": "posts tagged #{{tag}}"
"tagged": "posts tagged #{{tag}}",
"search": "searching for {{value}}"
},
"search": {
"posts_tab": "Posts",
"tags_tab": "Tags",
"profiles_tab": "Profiles"
},
"timeline": {
"by_before_handle": "By ",
Expand Down
Loading