Challenges

This commit is contained in:
LittleSheep 2023-12-11 23:12:51 +08:00
parent caff256949
commit 3f5654efb4
19 changed files with 421 additions and 56 deletions

5
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

12
.idea/Fuxi.iml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -0,0 +1,57 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<HTMLCodeStyleSettings>
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
</HTMLCodeStyleSettings>
<JSCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</JSCodeStyleSettings>
<TypeScriptCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</TypeScriptCodeStyleSettings>
<VueCodeStyleSettings>
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
</VueCodeStyleSettings>
<codeStyleSettings language="HTML">
<option name="SOFT_MARGINS" value="120" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="SOFT_MARGINS" value="120" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="TypeScript">
<option name="SOFT_MARGINS" value="120" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Vue">
<option name="SOFT_MARGINS" value="120" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Fuxi.iml" filepath="$PROJECT_DIR$/.idea/Fuxi.iml" />
</modules>
</component>
</project>

7
.idea/sqldialects.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/supabase/migrations/20231210135930_challenges.sql" dialect="GenericSQL" />
<file url="PROJECT" dialect="PostgreSQL" />
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -1,13 +1,15 @@
<template> <template>
<n-message-provider> <n-message-provider>
<nuxt-layout> <n-dialog-provider>
<nuxt-loading-indicator /> <nuxt-layout>
<nuxt-page /> <nuxt-loading-indicator />
</nuxt-layout> <nuxt-page />
</nuxt-layout>
</n-dialog-provider>
</n-message-provider> </n-message-provider>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { NMessageProvider } from "naive-ui"; import { NMessageProvider, NDialogProvider } from "naive-ui";
import "@/assets/css/index.css"; import "@/assets/css/index.css";
</script> </script>

Binary file not shown.

View File

@ -0,0 +1,11 @@
<template>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>

View File

@ -22,6 +22,7 @@
"vue-router": "^4.2.5" "vue-router": "^4.2.5"
}, },
"dependencies": { "dependencies": {
"@guolao/vue-monaco-editor": "^1.4.1",
"@ibm/plex": "^6.3.0", "@ibm/plex": "^6.3.0",
"@nuxtjs/mdc": "^0.2.8", "@nuxtjs/mdc": "^0.2.8",
"@supabase/supabase-js": "^2.39.0", "@supabase/supabase-js": "^2.39.0",

View File

@ -0,0 +1,30 @@
<template>
<div class="max-w-[720px] mx-auto">
<n-card>
<brand-header />
<div>Get everyone interested in programming.</div>
<n-divider class="mx-[-24px] w-[calc(100%+48px)]" />
<div class="text-gray text-xs">
<div>
<nuxt-link class="link" target="_blank" to="https://smartsheep.studio">Made by SmartSheep Studio.</nuxt-link>
Proprietary software.
</div>
<div>Fuxi Development Team © {{ new Date().getFullYear() }}</div>
</div>
</n-card>
</div>
</template>
<script setup lang="ts">
import { NCard, NDivider } from "naive-ui";
</script>
<style scoped>
.link {
all: unset;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,116 @@
<template>
<div class="md:max-w-[720px] mx-auto">
<n-card segmented>
<template #header>
<n-page-header @back="navigateTo('/')">
<template #title>Challenge #{{ challenge?.id }}</template>
<template #subtitle>
<div class="flex items-center gap-2">
<n-tag size="small" class="case-capital">{{ challenge?.status?.replaceAll("-", " ") }}</n-tag>
</div>
</template>
</n-page-header>
</template>
<client-only>
<vue-monaco-editor
:options="options"
v-model:value="answer.code"
class="min-h-[360px] code-editor"
/>
</client-only>
<n-divider class="mx-[-24px] w-[calc(100%+48px)] divider-below-code" />
<template #action>
<div class="w-full flex justify-between">
<div class="flex gap-2">
<n-button secondary circle class="rounded-[4px]" type="warning" :disabled="submitting" @click="save">
<template #icon>
<n-icon :component="Save" />
</template>
</n-button>
</div>
<div class="flex gap-2">
<n-button secondary type="error" :disabled="submitting" @click="abandon">放弃</n-button>
<n-button type="primary" :disabled="submitting" @click="submit">提交</n-button>
</div>
</div>
</template>
</n-card>
</div>
</template>
<script setup lang="ts">
import { NButton, NCard, NDivider, NPageHeader, NTag, NIcon, useDialog, useMessage } from "naive-ui";
import { VueMonacoEditor } from "@guolao/vue-monaco-editor";
import { Save } from "@vicons/carbon";
const route = useRoute();
const client = useSupabaseClient();
const message = useMessage();
const dialog = useDialog();
const options = {
minimap: { enabled: false }
};
const submitting = ref(false);
const { data: challenge } = await client
.from("challenges")
.select<any, any>("*")
.eq("id", route.params.id)
.single();
useHead({
title: challenge ? `挑战 #${challenge.id}` : "挑战 #404"
});
const answer = ref(challenge?.answer ?? {});
async function save() {
}
function abandon() {
const instance = dialog.warning({
title: "警告",
content: "你确定要放弃该次挑战?这会让该挑战立刻转化为放弃状态,并且扣除 5 点社会信用点,三思而后行!",
positiveText: "确定",
negativeText: "再试试",
onPositiveClick: async () => {
await client
.from("challenges")
// @ts-ignore
.update<any>({ status: "abandoned" })
.eq("id", challenge.id);
const delay = (ms: number) => new Promise(res => setTimeout(res, ms));
instance.loading = true
message.info("已放弃挑战该题");
await delay(1850);
window.close();
}
});
}
async function submit() {
}
</script>
<style scoped>
.code-editor {
min-width: calc(100% + 48px);
margin-top: -20px;
margin-left: -24px;
margin-right: -24px;
}
.divider-below-code {
margin-top: 0 !important;
}
</style>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="md:max-w-[720px] mx-auto"> <div class="md:max-w-[720px] mx-auto">
<problems-list /> <problem-list />
</div> </div>
</template> </template>

View File

@ -31,22 +31,90 @@
</div> </div>
<n-empty v-else description="本题无公开样例" /> <n-empty v-else description="本题无公开样例" />
</section> </section>
<template #action>
<div class="w-full flex justify-end">
<n-button v-if="!answering" type="primary" :loading="submitting" @click="doChallenge">试答该题</n-button>
<div v-else>
<n-button type="primary" disabled>
<template #icon>
<n-icon :component="Checkmark" />
</template>
正在作答
</n-button>
</div>
</div>
</template>
</n-card> </n-card>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { NCard, NTag, NPageHeader, NDivider, NEmpty } from "naive-ui"; import { NCard, NTag, NPageHeader, NDivider, NEmpty, NButton, NIcon, useMessage } from "naive-ui";
import { Checkmark } from "@vicons/carbon";
import "prismjs/themes/prism.css"; import "prismjs/themes/prism.css";
import "prismjs/prism"; import "prismjs/prism";
const user = useSupabaseUser();
const client = useSupabaseClient(); const client = useSupabaseClient();
const message = useMessage();
const route = useRoute(); const route = useRoute();
const { data: problem } = await client.from("problems").select<any, any>("*").eq("id", route.params.id).single(); const submitting = ref(false);
const answering = ref(false);
const { data: problem } = await client
.from("problems")
.select<any, any>("*")
.eq("id", route.params.id)
.single();
const { data: example } = await client const { data: example } = await client
.from("problem_cases") .from("problem_cases")
.select<any, any>("*") .select<any, any>("*")
.eq("problem", route.params.id) .eq("problem", route.params.id)
.single(); .single();
useHead({
title: problem?.title ?? "未知题目"
});
async function doChallenge() {
if (problem == null) return;
submitting.value = true;
let { data } = await client
.from("challenges")
.select<any, any>("id")
.eq("problem", problem?.id)
.eq("status", "in-progress")
.single();
if (data == null) {
const res = await client
.from("challenges")
// @ts-ignore
.insert<any>({
answers: {},
details: {},
status: "in-progress",
problem: problem.id,
author: user.value?.id
})
.select<any, any>()
.single();
if (res.error != null) {
message.error(`Something went wrong... ${res.error.message}`);
return;
} else {
data = res.data;
}
}
submitting.value = false;
answering.value = true;
navigateTo(`/challenges/${data.id}`, { open: { target: "_blank" } });
setTimeout(() => answering.value = false, 3000);
}
</script> </script>

View File

@ -1,14 +1,20 @@
create table public.profiles ( create table public.profiles
id uuid not null references auth.users on delete cascade, (
username varchar(64), id uuid not null references auth.users on delete cascade,
nickname varchar(256), username varchar(64),
school int8, nickname varchar(256),
primary key (id) school int8,
primary key (id)
); );
alter table public.profiles enable row level security;
alter table public.profiles
enable row level security;
create policy "Public profiles are viewable by everyone." on profiles for create policy "Public profiles are viewable by everyone." on profiles for
select using (true); select using (true);
create policy "Users can insert their own profile." on profiles for create policy "Users can insert their own profile." on profiles for
insert with check (auth.uid() = id); insert with check (auth.uid() = id);
create policy "Users can update own profile." on profiles for create policy "Users can update own profile." on profiles for
update using (auth.uid() = id); update using (auth.uid() = id);

View File

@ -1,30 +1,39 @@
create table public.problems ( create table public.problems
id bigint generated by default as identity, (
title text not null, id bigint generated by default as identity,
description text not null, title text not null,
type character varying not null, description text not null,
tags character varying [] null, type character varying not null,
author uuid null, tags character varying[] null,
metadata jsonb null, author uuid null,
is_draft boolean null default true, metadata jsonb null,
is_hidden boolean null default false, is_draft boolean null default true,
created_at timestamp with time zone null default now(), is_hidden boolean null default false,
constraint problems_pkey primary key (id), created_at timestamp with time zone null default now(),
constraint problems_author_fkey foreign key (author) references auth.users (id) constraint problems_pkey primary key (id),
constraint problems_author_fkey foreign key (author) references auth.users (id)
) tablespace pg_default; ) tablespace pg_default;
alter table public.problems enable row level security;
alter table public.problems
enable row level security;
create policy "Public problems are viewable by everyone." on problems for create policy "Public problems are viewable by everyone." on problems for
select using (true); select using (true);
create table public.problem_cases (
id bigint generated by default as identity, create table public.problem_cases
spec jsonb not null, (
limitations jsonb not null, id bigint generated by default as identity,
answer jsonb not null, spec jsonb not null,
problem int8 not null, limitations jsonb not null,
is_hidden bool not null default true, answer jsonb not null,
constraint problem_cases_pkey primary key (id), problem int8 not null,
constraint problem_cases_author_fkey foreign key (problem) references public.problems (id) is_hidden bool not null default true,
constraint problem_cases_pkey primary key (id),
constraint problem_cases_author_fkey foreign key (problem) references public.problems (id)
) tablespace pg_default; ) tablespace pg_default;
alter table public.problem_cases enable row level security;
alter table public.problem_cases
enable row level security;
create policy "Public problem cases are viewable by everyone." on problem_cases for create policy "Public problem cases are viewable by everyone." on problem_cases for
select using (is_hidden = false); select using (is_hidden = false);

View File

@ -1,14 +1,36 @@
create table public.challenges ( create table public.challenges
id bigint generated by default as identity, (
answers jsonb not null, id bigint generated by default as identity,
details jsonb not null, answers jsonb not null,
author uuid not null, details jsonb not null,
problem int8 not null, author uuid not null,
status varchar(256) not null, problem int8 not null,
created_at timestamp with time zone null default now(), status varchar(256) not null,
constraint challenges_pkey primary key (id), created_at timestamp with time zone null default now(),
constraint challenges_author_fkey foreign key (author) references auth.users (id), constraint challenges_pkey primary key (id),
constraint challenges_problem_fkey foreign key (problem) references public.problems (id) constraint challenges_author_fkey foreign key (author) references auth.users (id),
constraint challenges_problem_fkey foreign key (problem) references public.problems (id)
) tablespace pg_default; ) tablespace pg_default;
alter table public.challenges enable row level security; alter table public.challenges enable row level security;
create policy "The challagers can see their challenges" on "public"."challenges" for select to public using (auth.uid() = author) with check (true)
create
policy "Enable insert for everyone" on "public"."challenges"
as permissive for insert
to public
with check (true);
create
policy "Enable read access for users' own items" on "public"."challenges"
as permissive for
select
to public
using (author = auth.uid());
create
policy "Enable update access for users' own items" on "public"."challenges"
as permissive for
update
to public
using (author = auth.uid())
with check (author = auth.uid())